mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-15 02:05:59 +08:00
fix: Logs issues are fixed (#1727)
* feat: logs is updated * chore: width:100% is removed * chore: position of filter is updated * chore: min time and max time are now tracked from global state Co-authored-by: Pranay Prateek <pranay@signoz.io>
This commit is contained in:
parent
1273bb5865
commit
4c0d573760
@ -5,5 +5,4 @@ export const Container = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
`;
|
`;
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface OptionIconProps {
|
|
||||||
isDarkMode: boolean;
|
|
||||||
}
|
|
||||||
function OptionIcon({ isDarkMode }: OptionIconProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
width="1rem"
|
|
||||||
height="1rem"
|
|
||||||
viewBox="0 0 52 52"
|
|
||||||
enableBackground="new 0 0 52 52"
|
|
||||||
fill={isDarkMode ? '#eee' : '#222'}
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M20,44c0-3.3,2.7-6,6-6s6,2.7,6,6s-2.7,6-6,6S20,47.3,20,44z M20,26c0-3.3,2.7-6,6-6s6,2.7,6,6s-2.7,6-6,6
|
|
||||||
S20,29.3,20,26z M20,8c0-3.3,2.7-6,6-6s6,2.7,6,6s-2.7,6-6,6S20,11.3,20,8z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default OptionIcon;
|
|
26
frontend/src/container/LogLiveTail/config.ts
Normal file
26
frontend/src/container/LogLiveTail/config.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export const TIME_PICKER_OPTIONS = [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
label: '5m',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 15,
|
||||||
|
label: '15m',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 30,
|
||||||
|
label: '30m',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 60,
|
||||||
|
label: '1hr',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 360,
|
||||||
|
label: '6hrs',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 720,
|
||||||
|
label: '12hrs',
|
||||||
|
},
|
||||||
|
];
|
@ -1,7 +1,10 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
import { green } from '@ant-design/colors';
|
import { green } from '@ant-design/colors';
|
||||||
import { PauseOutlined, PlayCircleOutlined } from '@ant-design/icons';
|
import {
|
||||||
import { Button, Popover, Row, Select } from 'antd';
|
MoreOutlined,
|
||||||
|
PauseOutlined,
|
||||||
|
PlayCircleOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { Button, Popover, Select, Space } from 'antd';
|
||||||
import { LiveTail } from 'api/logs/livetail';
|
import { LiveTail } from 'api/logs/livetail';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { throttle } from 'lodash-es';
|
import { throttle } from 'lodash-es';
|
||||||
@ -18,38 +21,11 @@ import { TLogsLiveTailState } from 'types/api/logs/liveTail';
|
|||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
import OptionIcon from './OptionIcon';
|
import { TIME_PICKER_OPTIONS } from './config';
|
||||||
import { TimePickerCard, TimePickerSelect } from './styles';
|
import { StopContainer, TimePickerCard, TimePickerSelect } from './styles';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
const TIME_PICKER_OPTIONS = [
|
|
||||||
{
|
|
||||||
value: 5,
|
|
||||||
label: '5m',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 15,
|
|
||||||
label: '15m',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 30,
|
|
||||||
label: '30m',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 60,
|
|
||||||
label: '1hr',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 360,
|
|
||||||
label: '6hrs',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 720,
|
|
||||||
label: '12hrs',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function LogLiveTail(): JSX.Element {
|
function LogLiveTail(): JSX.Element {
|
||||||
const {
|
const {
|
||||||
liveTail,
|
liveTail,
|
||||||
@ -75,14 +51,12 @@ function LogLiveTail(): JSX.Element {
|
|||||||
type: PUSH_LIVE_TAIL_EVENT,
|
type: PUSH_LIVE_TAIL_EVENT,
|
||||||
payload: batchedEventsRef.current.reverse(),
|
payload: batchedEventsRef.current.reverse(),
|
||||||
});
|
});
|
||||||
// console.log('DISPATCH', batchedEventsRef.current.length);
|
|
||||||
batchedEventsRef.current = [];
|
batchedEventsRef.current = [];
|
||||||
}, 1500),
|
}, 1500),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const batchLiveLog = (e: { data: string }): void => {
|
const batchLiveLog = (e: { data: string }): void => {
|
||||||
// console.log('EVENT BATCHED');
|
|
||||||
batchedEventsRef.current.push(JSON.parse(e.data as string) as never);
|
batchedEventsRef.current.push(JSON.parse(e.data as string) as never);
|
||||||
pushLiveLog();
|
pushLiveLog();
|
||||||
};
|
};
|
||||||
@ -123,6 +97,7 @@ function LogLiveTail(): JSX.Element {
|
|||||||
if (liveTail === 'STOPPED') {
|
if (liveTail === 'STOPPED') {
|
||||||
liveTailSourceRef.current = null;
|
liveTailSourceRef.current = null;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [liveTail]);
|
}, [liveTail]);
|
||||||
|
|
||||||
const handleLiveTailStart = (): void => {
|
const handleLiveTailStart = (): void => {
|
||||||
@ -155,47 +130,39 @@ function LogLiveTail(): JSX.Element {
|
|||||||
),
|
),
|
||||||
[dispatch, liveTail, liveTailStartRange],
|
[dispatch, liveTail, liveTailStartRange],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TimePickerCard>
|
<TimePickerCard>
|
||||||
<Row
|
<Space size={0} align="center">
|
||||||
style={{ gap: '0.5rem', alignItems: 'center', justifyContent: 'center' }}
|
{liveTail === 'PLAYING' ? (
|
||||||
>
|
<Button
|
||||||
<div>
|
type="primary"
|
||||||
{liveTail === 'PLAYING' ? (
|
onClick={(): void => handleLiveTail('PAUSED')}
|
||||||
<Button
|
title="Pause live tail"
|
||||||
type="primary"
|
style={{ background: green[6] }}
|
||||||
onClick={(): void => handleLiveTail('PAUSED')}
|
>
|
||||||
title="Pause live tail"
|
<span>Pause</span>
|
||||||
style={{ background: green[6] }}
|
<PauseOutlined />
|
||||||
>
|
</Button>
|
||||||
Pause <PauseOutlined />
|
) : (
|
||||||
</Button>
|
<Button
|
||||||
) : (
|
type="primary"
|
||||||
<Button
|
onClick={handleLiveTailStart}
|
||||||
type="primary"
|
title="Start live tail"
|
||||||
onClick={handleLiveTailStart}
|
>
|
||||||
title="Start live tail"
|
Go Live <PlayCircleOutlined />
|
||||||
>
|
</Button>
|
||||||
Go Live <PlayCircleOutlined />
|
)}
|
||||||
</Button>
|
|
||||||
)}
|
{liveTail !== 'STOPPED' && (
|
||||||
{liveTail !== 'STOPPED' && (
|
<Button
|
||||||
<Button
|
type="dashed"
|
||||||
type="dashed"
|
onClick={(): void => handleLiveTail('STOPPED')}
|
||||||
onClick={(): void => handleLiveTail('STOPPED')}
|
title="Exit live tail"
|
||||||
title="Exit live tail"
|
>
|
||||||
>
|
<StopContainer isDarkMode={isDarkMode} />
|
||||||
<div
|
</Button>
|
||||||
style={{
|
)}
|
||||||
height: '0.8rem',
|
|
||||||
width: '0.8rem',
|
|
||||||
background: isDarkMode ? '#eee' : '#222',
|
|
||||||
borderRadius: '0.1rem',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Popover
|
<Popover
|
||||||
placement="bottomRight"
|
placement="bottomRight"
|
||||||
@ -203,18 +170,9 @@ function LogLiveTail(): JSX.Element {
|
|||||||
trigger="click"
|
trigger="click"
|
||||||
content={OptionsPopOverContent}
|
content={OptionsPopOverContent}
|
||||||
>
|
>
|
||||||
<span
|
<MoreOutlined style={{ fontSize: 24 }} />
|
||||||
style={{
|
|
||||||
padding: '0.3rem 0.4rem 0.3rem 0',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignContent: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<OptionIcon isDarkMode={isDarkMode} />
|
|
||||||
</span>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
</Row>
|
</Space>
|
||||||
</TimePickerCard>
|
</TimePickerCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
export const TimePickerCard = styled(Card)`
|
export const TimePickerCard = styled(Card)`
|
||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
|
display: flex;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -10,3 +11,15 @@ export const TimePickerCard = styled(Card)`
|
|||||||
export const TimePickerSelect = styled(Select)`
|
export const TimePickerSelect = styled(Select)`
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isDarkMode: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StopContainer = styled.div<Props>`
|
||||||
|
height: 0.8rem;
|
||||||
|
width: 0.8rem;
|
||||||
|
border-radius: 0.1rem;
|
||||||
|
background-color: ${({ isDarkMode }): string =>
|
||||||
|
isDarkMode ? '#fff' : '#000'};
|
||||||
|
`;
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
import { Divider, Row } from 'antd';
|
|
||||||
import LogControls from 'container/LogControls';
|
|
||||||
import LogDetailedView from 'container/LogDetailedView';
|
|
||||||
import LogLiveTail from 'container/LogLiveTail';
|
|
||||||
import LogsAggregate from 'container/LogsAggregate';
|
|
||||||
import LogsFilters from 'container/LogsFilters';
|
|
||||||
import SearchFilter from 'container/LogsSearchFilter';
|
|
||||||
import LogsTable from 'container/LogsTable';
|
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
|
||||||
import React, { memo, useEffect } from 'react';
|
|
||||||
import { connect, useDispatch } from 'react-redux';
|
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
|
||||||
import { GetLogsFields } from 'store/actions/logs/getFields';
|
|
||||||
import AppActions from 'types/actions';
|
|
||||||
import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs';
|
|
||||||
|
|
||||||
function Logs({ getLogsFields }: LogsProps): JSX.Element {
|
|
||||||
const urlQuery = useUrlQuery();
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch({
|
|
||||||
type: SET_SEARCH_QUERY_STRING,
|
|
||||||
payload: urlQuery.get('q'),
|
|
||||||
});
|
|
||||||
}, [dispatch, urlQuery]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getLogsFields();
|
|
||||||
}, [getLogsFields]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ position: 'relative' }}>
|
|
||||||
<Row style={{ justifyContent: 'center', alignItems: 'center' }}>
|
|
||||||
<SearchFilter />
|
|
||||||
<Divider type="vertical" style={{ height: '2rem' }} />
|
|
||||||
<LogLiveTail />
|
|
||||||
</Row>
|
|
||||||
<LogsAggregate />
|
|
||||||
<LogControls />
|
|
||||||
<Divider style={{ margin: 0 }} />
|
|
||||||
<Row gutter={20} style={{ flexWrap: 'nowrap' }}>
|
|
||||||
<LogsFilters />
|
|
||||||
<Divider type="vertical" style={{ height: '100%', margin: 0 }} />
|
|
||||||
<LogsTable />
|
|
||||||
</Row>
|
|
||||||
<LogDetailedView />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogsProps = DispatchProps;
|
|
||||||
|
|
||||||
interface DispatchProps {
|
|
||||||
getLogsFields: () => (dispatch: Dispatch<AppActions>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
|
||||||
): DispatchProps => ({
|
|
||||||
getLogsFields: bindActionCreators(GetLogsFields, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(memo(Logs));
|
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
import { blue } from '@ant-design/colors';
|
import { blue } from '@ant-design/colors';
|
||||||
import Graph from 'components/Graph';
|
import Graph from 'components/Graph';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
@ -16,9 +15,6 @@ import { ILogsReducer } from 'types/reducer/logs';
|
|||||||
|
|
||||||
import { Container } from './styles';
|
import { Container } from './styles';
|
||||||
|
|
||||||
interface LogsAggregateProps {
|
|
||||||
getLogsAggregate: (arg0: Parameters<typeof getLogsAggregate>[0]) => void;
|
|
||||||
}
|
|
||||||
function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
|
function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
searchFilter: { queryString },
|
searchFilter: { queryString },
|
||||||
@ -42,18 +38,18 @@ function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
|
|||||||
clearInterval(reFetchIntervalRef.current);
|
clearInterval(reFetchIntervalRef.current);
|
||||||
}
|
}
|
||||||
reFetchIntervalRef.current = null;
|
reFetchIntervalRef.current = null;
|
||||||
getLogsAggregate({
|
// getLogsAggregate({
|
||||||
timestampStart: minTime,
|
// timestampStart: minTime,
|
||||||
timestampEnd: maxTime,
|
// timestampEnd: maxTime,
|
||||||
step: getStep({
|
// step: getStep({
|
||||||
start: minTime,
|
// start: minTime,
|
||||||
end: maxTime,
|
// end: maxTime,
|
||||||
inputFormat: 'ns',
|
// inputFormat: 'ns',
|
||||||
}),
|
// }),
|
||||||
q: queryString,
|
// q: queryString,
|
||||||
...(idStart ? { idGt: idStart } : {}),
|
// ...(idStart ? { idGt: idStart } : {}),
|
||||||
...(idEnd ? { idLt: idEnd } : {}),
|
// ...(idEnd ? { idLt: idEnd } : {}),
|
||||||
});
|
// });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,18 +85,9 @@ function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [getLogsAggregate, maxTime, minTime, liveTail]);
|
}, [getLogsAggregate, maxTime, minTime, liveTail]);
|
||||||
|
|
||||||
const data = {
|
|
||||||
labels: logsAggregate.map((s) => new Date(s.timestamp / 1000000)),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
data: logsAggregate.map((s) => s.value),
|
|
||||||
backgroundColor: blue[4],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
{isLoadingAggregate ? (
|
{isLoadingAggregate ? (
|
||||||
@ -108,7 +95,15 @@ function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
|
|||||||
) : (
|
) : (
|
||||||
<Graph
|
<Graph
|
||||||
name="usage"
|
name="usage"
|
||||||
data={data}
|
data={{
|
||||||
|
labels: logsAggregate.map((s) => new Date(s.timestamp / 1000000)),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: logsAggregate.map((s) => s.value),
|
||||||
|
backgroundColor: blue[4],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
type="bar"
|
type="bar"
|
||||||
containerHeight="100%"
|
containerHeight="100%"
|
||||||
animate={false}
|
animate={false}
|
||||||
@ -118,6 +113,10 @@ function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LogsAggregateProps {
|
||||||
|
getLogsAggregate: (arg0: Parameters<typeof getLogsAggregate>[0]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
getLogsAggregate: (
|
getLogsAggregate: (
|
||||||
props: Parameters<typeof getLogsAggregate>[0],
|
props: Parameters<typeof getLogsAggregate>[0],
|
||||||
|
@ -21,9 +21,6 @@ import { CategoryContainer, Container, FieldContainer } from './styles';
|
|||||||
|
|
||||||
const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id'];
|
const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id'];
|
||||||
|
|
||||||
interface LogsFiltersProps {
|
|
||||||
getLogsFields: () => void;
|
|
||||||
}
|
|
||||||
function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
|
function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
fields: { interesting, selected },
|
fields: { interesting, selected },
|
||||||
@ -150,4 +147,6 @@ const mapDispatchToProps = (
|
|||||||
getLogsFields: bindActionCreators(GetLogsFields, dispatch),
|
getLogsFields: bindActionCreators(GetLogsFields, dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type LogsFiltersProps = DispatchProps;
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(memo(LogsFilters));
|
export default connect(null, mapDispatchToProps)(memo(LogsFilters));
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
/* eslint-disable react/no-array-index-key */
|
/* eslint-disable react/no-array-index-key */
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { CloseOutlined } from '@ant-design/icons';
|
import { CloseOutlined, CloseSquareOutlined } from '@ant-design/icons';
|
||||||
import { Button, Input, Select } from 'antd';
|
import { Button, Input, Select } from 'antd';
|
||||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||||
import {
|
import {
|
||||||
@ -19,12 +19,46 @@ import { AppState } from 'store/reducers';
|
|||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import { SearchFieldsProps } from '..';
|
||||||
import FieldKey from '../FieldKey';
|
import FieldKey from '../FieldKey';
|
||||||
import { QueryConditionContainer, QueryFieldContainer } from '../styles';
|
import { QueryConditionContainer, QueryFieldContainer } from '../styles';
|
||||||
import { createParsedQueryStructure } from '../utils';
|
import { createParsedQueryStructure } from '../utils';
|
||||||
|
import { Container, QueryWrapper } from './styles';
|
||||||
import { hashCode, parseQuery } from './utils';
|
import { hashCode, parseQuery } from './utils';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
|
function QueryConditionField({
|
||||||
|
query,
|
||||||
|
queryIndex,
|
||||||
|
onUpdate,
|
||||||
|
style,
|
||||||
|
}: QueryConditionFieldProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<QueryConditionContainer style={{ ...style }}>
|
||||||
|
<Select
|
||||||
|
defaultValue={
|
||||||
|
(query as any).value &&
|
||||||
|
(((query as any)?.value as any) as string).toUpperCase()
|
||||||
|
}
|
||||||
|
onChange={(e): void => {
|
||||||
|
onUpdate({ ...query, value: e }, queryIndex);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.values(ConditionalOperators).map((cond) => (
|
||||||
|
<Option key={cond} value={cond} label={cond}>
|
||||||
|
{cond}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</QueryConditionContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryConditionField.defaultProps = {
|
||||||
|
style: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
interface QueryFieldProps {
|
interface QueryFieldProps {
|
||||||
query: Query;
|
query: Query;
|
||||||
queryIndex: number;
|
queryIndex: number;
|
||||||
@ -140,41 +174,15 @@ interface QueryConditionFieldProps {
|
|||||||
query: { value: string | string[]; type: string }[];
|
query: { value: string | string[]; type: string }[];
|
||||||
queryIndex: number;
|
queryIndex: number;
|
||||||
onUpdate: (arg0: unknown, arg1: number) => void;
|
onUpdate: (arg0: unknown, arg1: number) => void;
|
||||||
}
|
style?: React.CSSProperties;
|
||||||
function QueryConditionField({
|
|
||||||
query,
|
|
||||||
queryIndex,
|
|
||||||
onUpdate,
|
|
||||||
}: QueryConditionFieldProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<QueryConditionContainer>
|
|
||||||
<Select
|
|
||||||
defaultValue={
|
|
||||||
(query as any).value &&
|
|
||||||
(((query as any)?.value as any) as string).toUpperCase()
|
|
||||||
}
|
|
||||||
onChange={(e): void => {
|
|
||||||
onUpdate({ ...query, value: e }, queryIndex);
|
|
||||||
}}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
>
|
|
||||||
{Object.values(ConditionalOperators).map((cond) => (
|
|
||||||
<Option key={cond} value={cond} label={cond}>
|
|
||||||
{cond}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</QueryConditionContainer>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Query = { value: string | string[]; type: string }[];
|
export type Query = { value: string | string[]; type: string }[];
|
||||||
|
|
||||||
function QueryBuilder({
|
function QueryBuilder({
|
||||||
updateParsedQuery,
|
updateParsedQuery,
|
||||||
}: {
|
onDropDownToggleHandler,
|
||||||
updateParsedQuery: (arg0: unknown) => void;
|
}: SearchFieldsProps): JSX.Element {
|
||||||
}): JSX.Element {
|
|
||||||
const {
|
const {
|
||||||
searchFilter: { parsedQuery },
|
searchFilter: { parsedQuery },
|
||||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||||
@ -233,19 +241,16 @@ function QueryBuilder({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<CategoryHeading>LOG QUERY BUILDER</CategoryHeading>
|
<Container isMargin={generatedQueryStructure.length === 0}>
|
||||||
<div
|
<CategoryHeading>LOG QUERY BUILDER</CategoryHeading>
|
||||||
style={{
|
<CloseSquareOutlined onClick={onDropDownToggleHandler(false)} />
|
||||||
display: 'grid',
|
</Container>
|
||||||
gridTemplateColumns: '80px 1fr',
|
|
||||||
margin: '0.5rem 0',
|
<QueryWrapper>{QueryUI()}</QueryWrapper>
|
||||||
}}
|
</>
|
||||||
>
|
|
||||||
{QueryUI()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isMargin: boolean;
|
||||||
|
}
|
||||||
|
export const Container = styled.div<Props>`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: ${(props): string => (props.isMargin ? '2rem' : '0')};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const QueryWrapper = styled.div`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 80px 1fr;
|
||||||
|
margin: 0.5rem 0px;
|
||||||
|
`;
|
@ -1,6 +1,6 @@
|
|||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||||
import { map } from 'lodash-es';
|
import map from 'lodash-es/map';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
@ -2,14 +2,23 @@ import React from 'react';
|
|||||||
|
|
||||||
import QueryBuilder from './QueryBuilder/QueryBuilder';
|
import QueryBuilder from './QueryBuilder/QueryBuilder';
|
||||||
import Suggestions from './Suggestions';
|
import Suggestions from './Suggestions';
|
||||||
|
import { QueryFields } from './utils';
|
||||||
|
|
||||||
interface SearchFieldsProps {
|
export interface SearchFieldsProps {
|
||||||
updateParsedQuery: () => void;
|
updateParsedQuery: (query: QueryFields[]) => void;
|
||||||
|
onDropDownToggleHandler: (value: boolean) => VoidFunction;
|
||||||
}
|
}
|
||||||
function SearchFields({ updateParsedQuery }: SearchFieldsProps): JSX.Element {
|
|
||||||
|
function SearchFields({
|
||||||
|
updateParsedQuery,
|
||||||
|
onDropDownToggleHandler,
|
||||||
|
}: SearchFieldsProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<QueryBuilder updateParsedQuery={updateParsedQuery} />
|
<QueryBuilder
|
||||||
|
onDropDownToggleHandler={onDropDownToggleHandler}
|
||||||
|
updateParsedQuery={updateParsedQuery}
|
||||||
|
/>
|
||||||
<Suggestions />
|
<Suggestions />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -9,12 +9,13 @@ export const QueryFieldContainer = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
width: 100%;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: ${blue[6]};
|
background: ${blue[6]};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const QueryConditionContainer = styled.div`
|
export const QueryConditionContainer = styled.div`
|
||||||
padding: 0.25rem 0rem;
|
display: flex;
|
||||||
margin: 0.1rem 0;
|
flex-direction: row;
|
||||||
`;
|
`;
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
import { Input, InputRef, Popover } from 'antd';
|
||||||
import { CloseSquareOutlined } from '@ant-design/icons';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { Button, Input } from 'antd';
|
|
||||||
import useClickOutside from 'hooks/useClickOutside';
|
|
||||||
import getStep from 'lib/getStep';
|
import getStep from 'lib/getStep';
|
||||||
import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||||
import { useLocation } from 'react-use';
|
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import { getLogs } from 'store/actions/logs/getLogs';
|
import { getLogs } from 'store/actions/logs/getLogs';
|
||||||
@ -17,17 +14,9 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
import SearchFields from './SearchFields';
|
import SearchFields from './SearchFields';
|
||||||
import { DropDownContainer } from './styles';
|
import { Container, DropDownContainer } from './styles';
|
||||||
import { useSearchParser } from './useSearchParser';
|
import { useSearchParser } from './useSearchParser';
|
||||||
|
|
||||||
const { Search } = Input;
|
|
||||||
|
|
||||||
interface SearchFilterProps {
|
|
||||||
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
|
|
||||||
getLogsAggregate: (
|
|
||||||
props: Parameters<typeof getLogsAggregate>[0],
|
|
||||||
) => ReturnType<typeof getLogsAggregate>;
|
|
||||||
}
|
|
||||||
function SearchFilter({
|
function SearchFilter({
|
||||||
getLogs,
|
getLogs,
|
||||||
getLogsAggregate,
|
getLogsAggregate,
|
||||||
@ -38,6 +27,14 @@ function SearchFilter({
|
|||||||
updateQueryString,
|
updateQueryString,
|
||||||
} = useSearchParser();
|
} = useSearchParser();
|
||||||
const [showDropDown, setShowDropDown] = useState(false);
|
const [showDropDown, setShowDropDown] = useState(false);
|
||||||
|
const searchRef = useRef<InputRef>(null);
|
||||||
|
|
||||||
|
const onDropDownToggleHandler = useCallback(
|
||||||
|
(value: boolean) => (): void => {
|
||||||
|
setShowDropDown(value);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const { logLinesPerPage, idEnd, idStart, liveTail } = useSelector<
|
const { logLinesPerPage, idEnd, idStart, liveTail } = useSelector<
|
||||||
AppState,
|
AppState,
|
||||||
@ -48,117 +45,104 @@ function SearchFilter({
|
|||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
const searchComponentRef = useRef<HTMLDivElement>(null);
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
|
||||||
useClickOutside(searchComponentRef, (e: HTMLElement) => {
|
const handleSearch = useCallback(
|
||||||
// using this hack as overlay span is voilating this condition
|
(customQuery) => {
|
||||||
if (
|
if (liveTail === 'PLAYING') {
|
||||||
e.nodeName === 'svg' ||
|
dispatch({
|
||||||
e.nodeName === 'path' ||
|
type: TOGGLE_LIVE_TAIL,
|
||||||
e.nodeName === 'span' ||
|
payload: 'PAUSED',
|
||||||
e.nodeName === 'button'
|
});
|
||||||
) {
|
setTimeout(
|
||||||
return;
|
() =>
|
||||||
}
|
dispatch({
|
||||||
|
type: TOGGLE_LIVE_TAIL,
|
||||||
|
payload: liveTail,
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
getLogs({
|
||||||
|
q: customQuery,
|
||||||
|
limit: logLinesPerPage,
|
||||||
|
orderBy: 'timestamp',
|
||||||
|
order: 'desc',
|
||||||
|
timestampStart: minTime,
|
||||||
|
timestampEnd: maxTime,
|
||||||
|
...(idStart ? { idGt: idStart } : {}),
|
||||||
|
...(idEnd ? { idLt: idEnd } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
if (
|
getLogsAggregate({
|
||||||
e.nodeName === 'DIV' &&
|
timestampStart: minTime,
|
||||||
![
|
timestampEnd: maxTime,
|
||||||
'ant-empty-image',
|
step: getStep({
|
||||||
'ant-select-item',
|
start: minTime,
|
||||||
'ant-col',
|
end: maxTime,
|
||||||
'ant-select-item-option-content',
|
inputFormat: 'ns',
|
||||||
'ant-select-item-option-active',
|
|
||||||
].find((p) => p.indexOf(e.className) !== -1) &&
|
|
||||||
!(e.ariaSelected === 'true') &&
|
|
||||||
showDropDown
|
|
||||||
) {
|
|
||||||
setShowDropDown(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const { search } = useLocation();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const handleSearch = (customQuery = ''): void => {
|
|
||||||
if (liveTail === 'PLAYING') {
|
|
||||||
dispatch({
|
|
||||||
type: TOGGLE_LIVE_TAIL,
|
|
||||||
payload: 'PAUSED',
|
|
||||||
});
|
|
||||||
setTimeout(
|
|
||||||
() =>
|
|
||||||
dispatch({
|
|
||||||
type: TOGGLE_LIVE_TAIL,
|
|
||||||
payload: liveTail,
|
|
||||||
}),
|
}),
|
||||||
0,
|
q: customQuery,
|
||||||
);
|
});
|
||||||
} else {
|
}
|
||||||
getLogs({
|
},
|
||||||
q: customQuery || queryString,
|
[
|
||||||
limit: logLinesPerPage,
|
dispatch,
|
||||||
orderBy: 'timestamp',
|
getLogs,
|
||||||
order: 'desc',
|
getLogsAggregate,
|
||||||
timestampStart: minTime,
|
idEnd,
|
||||||
timestampEnd: maxTime,
|
idStart,
|
||||||
...(idStart ? { idGt: idStart } : {}),
|
liveTail,
|
||||||
...(idEnd ? { idLt: idEnd } : {}),
|
logLinesPerPage,
|
||||||
});
|
maxTime,
|
||||||
|
minTime,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
getLogsAggregate({
|
const urlQuery = useUrlQuery();
|
||||||
timestampStart: minTime,
|
const urlQueryString = urlQuery.get('q');
|
||||||
timestampEnd: maxTime,
|
|
||||||
step: getStep({
|
|
||||||
start: minTime,
|
|
||||||
end: maxTime,
|
|
||||||
inputFormat: 'ns',
|
|
||||||
}),
|
|
||||||
q: customQuery || queryString,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setShowDropDown(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const urlQuery = useMemo(() => {
|
|
||||||
return new URLSearchParams(search);
|
|
||||||
}, [search]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const urlQueryString = urlQuery.get('q');
|
handleSearch(urlQueryString || '');
|
||||||
if (urlQueryString !== null) handleSearch(urlQueryString);
|
}, [handleSearch, urlQueryString]);
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={searchComponentRef} style={{ flex: 1 }}>
|
<Container>
|
||||||
<Search
|
<Popover
|
||||||
placeholder="Search Filter"
|
placement="bottom"
|
||||||
onFocus={(): void => setShowDropDown(true)}
|
content={
|
||||||
value={queryString}
|
|
||||||
onChange={(e): void => {
|
|
||||||
updateQueryString(e.target.value);
|
|
||||||
}}
|
|
||||||
onSearch={handleSearch}
|
|
||||||
/>
|
|
||||||
<div style={{ position: 'relative' }}>
|
|
||||||
{showDropDown && (
|
|
||||||
<DropDownContainer>
|
<DropDownContainer>
|
||||||
<Button
|
<SearchFields
|
||||||
type="text"
|
onDropDownToggleHandler={onDropDownToggleHandler}
|
||||||
onClick={(): void => setShowDropDown(false)}
|
updateParsedQuery={updateParsedQuery as never}
|
||||||
style={{
|
/>
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseSquareOutlined />
|
|
||||||
</Button>
|
|
||||||
<SearchFields updateParsedQuery={updateParsedQuery as never} />
|
|
||||||
</DropDownContainer>
|
</DropDownContainer>
|
||||||
)}
|
}
|
||||||
</div>
|
trigger="click"
|
||||||
</div>
|
overlayInnerStyle={{
|
||||||
|
width: `${searchRef?.current?.input?.offsetWidth || 0}px`,
|
||||||
|
}}
|
||||||
|
visible={showDropDown}
|
||||||
|
destroyTooltipOnHide
|
||||||
|
onVisibleChange={(value): void => {
|
||||||
|
onDropDownToggleHandler(value)();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input.Search
|
||||||
|
ref={searchRef}
|
||||||
|
placeholder="Search Filter"
|
||||||
|
value={queryString}
|
||||||
|
onChange={(e): void => {
|
||||||
|
updateQueryString(e.target.value);
|
||||||
|
}}
|
||||||
|
allowClear
|
||||||
|
onSearch={handleSearch}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
getLogs: (
|
getLogs: (
|
||||||
props: Parameters<typeof getLogs>[0],
|
props: Parameters<typeof getLogs>[0],
|
||||||
@ -168,6 +152,8 @@ interface DispatchProps {
|
|||||||
) => (dispatch: Dispatch<AppActions>) => void;
|
) => (dispatch: Dispatch<AppActions>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SearchFilterProps = DispatchProps;
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
const mapDispatchToProps = (
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
): DispatchProps => ({
|
): DispatchProps => ({
|
||||||
@ -175,4 +161,4 @@ const mapDispatchToProps = (
|
|||||||
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
|
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(memo(SearchFilter));
|
export default connect(null, mapDispatchToProps)(SearchFilter);
|
||||||
|
@ -2,11 +2,13 @@ import { Card } from 'antd';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const DropDownContainer = styled(Card)`
|
export const DropDownContainer = styled(Card)`
|
||||||
top: 0.5rem;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
padding: 0.8rem;
|
width: 100%;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const Container = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
@ -23,10 +23,12 @@ export function useSearchParser(): {
|
|||||||
|
|
||||||
const updateQueryString = useCallback(
|
const updateQueryString = useCallback(
|
||||||
(updatedQueryString) => {
|
(updatedQueryString) => {
|
||||||
history.push({
|
if (updatedQueryString) {
|
||||||
pathname: history.location.pathname,
|
history.push({
|
||||||
search: updatedQueryString ? `?q=${updatedQueryString}` : '',
|
pathname: history.location.pathname,
|
||||||
});
|
search: updatedQueryString ? `?q=${updatedQueryString}` : '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SET_SEARCH_QUERY_STRING,
|
type: SET_SEARCH_QUERY_STRING,
|
||||||
|
@ -3,48 +3,18 @@ import { Typography } from 'antd';
|
|||||||
import LogItem from 'components/Logs/LogItem';
|
import LogItem from 'components/Logs/LogItem';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { map } from 'lodash-es';
|
import { map } from 'lodash-es';
|
||||||
import React, { memo, useEffect } from 'react';
|
import React, { memo } from 'react';
|
||||||
import { connect, useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
|
||||||
import { getLogs } from 'store/actions/logs/getLogs';
|
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
import { Container, Heading } from './styles';
|
import { Container, Heading } from './styles';
|
||||||
|
|
||||||
function LogsTable({ getLogs }: LogsTableProps): JSX.Element {
|
function LogsTable(): JSX.Element {
|
||||||
const {
|
const { logs, isLoading, liveTail } = useSelector<AppState, ILogsReducer>(
|
||||||
searchFilter: { queryString },
|
(state) => state.logs,
|
||||||
logs,
|
|
||||||
logLinesPerPage,
|
|
||||||
idEnd,
|
|
||||||
idStart,
|
|
||||||
isLoading,
|
|
||||||
liveTail,
|
|
||||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
|
||||||
|
|
||||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
|
||||||
(state) => state.globalTime,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (liveTail === 'STOPPED')
|
|
||||||
getLogs({
|
|
||||||
q: queryString,
|
|
||||||
limit: logLinesPerPage,
|
|
||||||
orderBy: 'timestamp',
|
|
||||||
order: 'desc',
|
|
||||||
timestampStart: minTime,
|
|
||||||
timestampEnd: maxTime,
|
|
||||||
...(idStart ? { idGt: idStart } : {}),
|
|
||||||
...(idEnd ? { idLt: idEnd } : {}),
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [getLogs, idEnd, idStart, logLinesPerPage, maxTime, minTime, liveTail]);
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Spinner height={20} tip="Getting Logs" />;
|
return <Spinner height={20} tip="Getting Logs" />;
|
||||||
}
|
}
|
||||||
@ -72,20 +42,4 @@ function LogsTable({ getLogs }: LogsTableProps): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DispatchProps {
|
export default memo(LogsTable);
|
||||||
getLogs: (
|
|
||||||
props: Parameters<typeof getLogs>[0],
|
|
||||||
) => (dispatch: Dispatch<AppActions>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
|
||||||
): DispatchProps => ({
|
|
||||||
getLogs: bindActionCreators(getLogs, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
interface LogsTableProps {
|
|
||||||
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(memo(LogsTable));
|
|
||||||
|
@ -1,8 +1,72 @@
|
|||||||
import Logs from 'container/Logs';
|
import { Divider, Row } from 'antd';
|
||||||
import React from 'react';
|
import LogControls from 'container/LogControls';
|
||||||
|
import LogDetailedView from 'container/LogDetailedView';
|
||||||
|
import LogLiveTail from 'container/LogLiveTail';
|
||||||
|
import LogsAggregate from 'container/LogsAggregate';
|
||||||
|
import LogsFilters from 'container/LogsFilters';
|
||||||
|
import LogsSearchFilter from 'container/LogsSearchFilter';
|
||||||
|
import LogsTable from 'container/LogsTable';
|
||||||
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
|
import React, { memo, useEffect } from 'react';
|
||||||
|
import { connect, useDispatch } from 'react-redux';
|
||||||
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
|
import { GetLogsFields } from 'store/actions/logs/getFields';
|
||||||
|
import AppActions from 'types/actions';
|
||||||
|
import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs';
|
||||||
|
|
||||||
function LogsHome(): JSX.Element {
|
import SpaceContainer from './styles';
|
||||||
return <Logs />;
|
|
||||||
|
function Logs({ getLogsFields }: LogsProps): JSX.Element {
|
||||||
|
const urlQuery = useUrlQuery();
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch({
|
||||||
|
type: SET_SEARCH_QUERY_STRING,
|
||||||
|
payload: urlQuery.get('q'),
|
||||||
|
});
|
||||||
|
}, [dispatch, urlQuery]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getLogsFields();
|
||||||
|
}, [getLogsFields]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SpaceContainer
|
||||||
|
split={<Divider type="vertical" />}
|
||||||
|
align="center"
|
||||||
|
direction="horizontal"
|
||||||
|
>
|
||||||
|
<LogsSearchFilter />
|
||||||
|
<LogLiveTail />
|
||||||
|
</SpaceContainer>
|
||||||
|
|
||||||
|
<LogsAggregate />
|
||||||
|
<LogControls />
|
||||||
|
<Divider plain orientationMargin={1} />
|
||||||
|
<Row gutter={20} wrap={false}>
|
||||||
|
<LogsFilters />
|
||||||
|
<Divider type="vertical" />
|
||||||
|
<LogsTable />
|
||||||
|
</Row>
|
||||||
|
<LogDetailedView />
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LogsHome;
|
type LogsProps = DispatchProps;
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
getLogsFields: () => (dispatch: Dispatch<AppActions>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (
|
||||||
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
|
): DispatchProps => ({
|
||||||
|
getLogsFields: bindActionCreators(GetLogsFields, dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(memo(Logs));
|
||||||
|
10
frontend/src/pages/Logs/styles.ts
Normal file
10
frontend/src/pages/Logs/styles.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Space } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const SpaceContainer = styled(Space)`
|
||||||
|
.ant-space-item:nth-child(1) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default SpaceContainer;
|
@ -99,15 +99,14 @@ export const LogsReducer = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
case ADD_SEARCH_FIELD_QUERY_STRING: {
|
case ADD_SEARCH_FIELD_QUERY_STRING: {
|
||||||
const updatedQueryString =
|
const updatedQueryString = `${state?.searchFilter?.queryString || ''}${
|
||||||
state.searchFilter.queryString ||
|
state.searchFilter.queryString && state.searchFilter.queryString.length > 0
|
||||||
`${
|
? ' and '
|
||||||
state.searchFilter.queryString && state.searchFilter.queryString.length > 0
|
: ''
|
||||||
? ' and '
|
}${action.payload}`;
|
||||||
: ''
|
|
||||||
}${action.payload}`;
|
|
||||||
|
|
||||||
const updatedParsedQuery = parseQuery(updatedQueryString);
|
const updatedParsedQuery = parseQuery(updatedQueryString);
|
||||||
|
console.log({ updatedParsedQuery, updatedQueryString, action });
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
searchFilter: {
|
searchFilter: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user