[SIEM] Add support for IP details flow target in url (#54546)
This commit is contained in:
parent
884fe91c1e
commit
b758b78e1e
|
@ -12,6 +12,7 @@ import { TestProviders } from '../../../mock';
|
|||
import { getEmptyStringTag } from '../../empty_value';
|
||||
import { HostDetailsLink, IPDetailsLink } from '../../links';
|
||||
import { useMountAppended } from '../../../utils/use_mount_appended';
|
||||
import { FlowTarget } from '../../../graphql/types';
|
||||
|
||||
jest.mock('../../search_bar', () => ({
|
||||
siemFilterManager: {
|
||||
|
@ -91,13 +92,15 @@ describe('PointToolTipContent', () => {
|
|||
|
||||
test('it returns IPDetailsLink if field is source.ip', () => {
|
||||
const value = '127.0.0.1';
|
||||
expect(getRenderedFieldValue('source.ip', value)).toStrictEqual(<IPDetailsLink ip={value} />);
|
||||
expect(getRenderedFieldValue('source.ip', value)).toStrictEqual(
|
||||
<IPDetailsLink ip={value} flowTarget={FlowTarget.source} />
|
||||
);
|
||||
});
|
||||
|
||||
test('it returns IPDetailsLink if field is destination.ip', () => {
|
||||
const value = '127.0.0.1';
|
||||
expect(getRenderedFieldValue('destination.ip', value)).toStrictEqual(
|
||||
<IPDetailsLink ip={value} />
|
||||
<IPDetailsLink ip={value} flowTarget={FlowTarget.destination} />
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import { DescriptionListStyled } from '../../page';
|
|||
import { FeatureProperty } from '../types';
|
||||
import { HostDetailsLink, IPDetailsLink } from '../../links';
|
||||
import { DefaultFieldRenderer } from '../../field_renderers/field_renderers';
|
||||
import { FlowTarget } from '../../../graphql/types';
|
||||
|
||||
interface PointToolTipContentProps {
|
||||
contextId: string;
|
||||
|
@ -66,7 +67,8 @@ export const getRenderedFieldValue = (field: string, value: string) => {
|
|||
} else if (['host.name'].includes(field)) {
|
||||
return <HostDetailsLink hostName={value} />;
|
||||
} else if (['source.ip', 'destination.ip'].includes(field)) {
|
||||
return <IPDetailsLink ip={value} />;
|
||||
const flowTarget = field.split('.')[0] as FlowTarget;
|
||||
return <IPDetailsLink ip={value} flowTarget={flowTarget} />;
|
||||
}
|
||||
return <>{value}</>;
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@ exports[`FlowTargetSelect Component rendering it renders the FlowTargetSelect 1`
|
|||
hasDividers={false}
|
||||
isInvalid={false}
|
||||
isLoading={false}
|
||||
onChange={[Function]}
|
||||
onChange={[MockFunction]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import { mount, shallow } from 'enzyme';
|
||||
import { clone } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
|
||||
import { FlowDirection, FlowTarget } from '../../graphql/types';
|
||||
|
||||
|
@ -21,9 +20,7 @@ describe('FlowTargetSelect Component', () => {
|
|||
selectedDirection: FlowDirection.uniDirectional,
|
||||
isLoading: false,
|
||||
selectedTarget: FlowTarget.source,
|
||||
updateFlowTargetAction: (jest.fn() as unknown) as ActionCreator<{
|
||||
flowTarget: FlowTarget;
|
||||
}>,
|
||||
updateFlowTargetAction: jest.fn(),
|
||||
};
|
||||
|
||||
describe('rendering', () => {
|
||||
|
@ -51,10 +48,7 @@ describe('FlowTargetSelect Component', () => {
|
|||
|
||||
wrapper.update();
|
||||
|
||||
// @ts-ignore property mock does not exists
|
||||
expect(mockProps.updateFlowTargetAction.mock.calls[0][0]).toEqual({
|
||||
flowTarget: 'destination',
|
||||
});
|
||||
expect(mockProps.updateFlowTargetAction.mock.calls[0][0]).toEqual('destination');
|
||||
});
|
||||
|
||||
test('when selectedDirection=unidirectional only source/destination are options', () => {
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
*/
|
||||
|
||||
import { EuiSuperSelect } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
import React from 'react';
|
||||
|
||||
import { FlowDirection, FlowTarget } from '../../graphql/types';
|
||||
|
||||
|
@ -45,47 +44,31 @@ interface OwnProps {
|
|||
selectedTarget: FlowTarget;
|
||||
displayTextOverride?: string[];
|
||||
selectedDirection?: FlowDirection;
|
||||
updateFlowTargetAction: ActionCreator<{ flowTarget: FlowTarget }>;
|
||||
updateFlowTargetAction: (flowTarget: FlowTarget) => void;
|
||||
}
|
||||
|
||||
const onChangeTarget = (
|
||||
flowTarget: FlowTarget,
|
||||
updateFlowTargetSelectAction: ActionCreator<{ flowTarget: FlowTarget }>
|
||||
) => {
|
||||
updateFlowTargetSelectAction({ flowTarget });
|
||||
};
|
||||
|
||||
export type FlowTargetSelectProps = OwnProps;
|
||||
|
||||
export const FlowTargetSelect = React.memo<FlowTargetSelectProps>(
|
||||
({
|
||||
id,
|
||||
isLoading = false,
|
||||
selectedDirection,
|
||||
selectedTarget,
|
||||
displayTextOverride = [],
|
||||
updateFlowTargetAction,
|
||||
}) => {
|
||||
const handleChange = useCallback(
|
||||
(newFlowTarget: FlowTarget) => onChangeTarget(newFlowTarget, updateFlowTargetAction),
|
||||
[updateFlowTargetAction]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiSuperSelect
|
||||
options={
|
||||
selectedDirection
|
||||
? toggleTargetOptions(id, displayTextOverride).filter(option =>
|
||||
option.directions.includes(selectedDirection)
|
||||
)
|
||||
: toggleTargetOptions(id, displayTextOverride)
|
||||
}
|
||||
valueOfSelected={selectedTarget}
|
||||
onChange={handleChange}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const FlowTargetSelectComponent: React.FC<FlowTargetSelectProps> = ({
|
||||
id,
|
||||
isLoading = false,
|
||||
selectedDirection,
|
||||
selectedTarget,
|
||||
displayTextOverride = [],
|
||||
updateFlowTargetAction,
|
||||
}) => (
|
||||
<EuiSuperSelect
|
||||
options={
|
||||
selectedDirection
|
||||
? toggleTargetOptions(id, displayTextOverride).filter(option =>
|
||||
option.directions.includes(selectedDirection)
|
||||
)
|
||||
: toggleTargetOptions(id, displayTextOverride)
|
||||
}
|
||||
valueOfSelected={selectedTarget}
|
||||
onChange={updateFlowTargetAction}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
);
|
||||
|
||||
FlowTargetSelect.displayName = 'FlowTargetSelect';
|
||||
export const FlowTargetSelect = React.memo(FlowTargetSelectComponent);
|
||||
|
|
|
@ -59,13 +59,13 @@ const getDataProvider = ({
|
|||
and: [],
|
||||
});
|
||||
|
||||
const NonDecoratedIp = React.memo<{
|
||||
const NonDecoratedIpComponent: React.FC<{
|
||||
contextId: string;
|
||||
eventId: string;
|
||||
fieldName: string;
|
||||
truncate?: boolean;
|
||||
value: string | object | null | undefined;
|
||||
}>(({ contextId, eventId, fieldName, truncate, value }) => (
|
||||
}> = ({ contextId, eventId, fieldName, truncate, value }) => (
|
||||
<DraggableWrapper
|
||||
dataProvider={getDataProvider({ contextId, eventId, fieldName, address: value })}
|
||||
key={`non-decorated-ip-draggable-wrapper-${getUniqueId({
|
||||
|
@ -87,17 +87,17 @@ const NonDecoratedIp = React.memo<{
|
|||
}
|
||||
truncate={truncate}
|
||||
/>
|
||||
));
|
||||
);
|
||||
|
||||
NonDecoratedIp.displayName = 'NonDecoratedIp';
|
||||
const NonDecoratedIp = React.memo(NonDecoratedIpComponent);
|
||||
|
||||
const AddressLinks = React.memo<{
|
||||
const AddressLinksComponent: React.FC<{
|
||||
addresses: string[];
|
||||
contextId: string;
|
||||
eventId: string;
|
||||
fieldName: string;
|
||||
truncate?: boolean;
|
||||
}>(({ addresses, contextId, eventId, fieldName, truncate }) => (
|
||||
}> = ({ addresses, contextId, eventId, fieldName, truncate }) => (
|
||||
<>
|
||||
{uniq(addresses).map(address => (
|
||||
<DraggableWrapper
|
||||
|
@ -123,17 +123,17 @@ const AddressLinks = React.memo<{
|
|||
/>
|
||||
))}
|
||||
</>
|
||||
));
|
||||
);
|
||||
|
||||
AddressLinks.displayName = 'AddressLinks';
|
||||
const AddressLinks = React.memo(AddressLinksComponent);
|
||||
|
||||
export const FormattedIp = React.memo<{
|
||||
const FormattedIpComponent: React.FC<{
|
||||
contextId: string;
|
||||
eventId: string;
|
||||
fieldName: string;
|
||||
truncate?: boolean;
|
||||
value: string | object | null | undefined;
|
||||
}>(({ contextId, eventId, fieldName, truncate, value }) => {
|
||||
}> = ({ contextId, eventId, fieldName, truncate, value }) => {
|
||||
if (isString(value) && !isEmpty(value)) {
|
||||
try {
|
||||
const addresses = JSON.parse(value);
|
||||
|
@ -173,6 +173,6 @@ export const FormattedIp = React.memo<{
|
|||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
FormattedIp.displayName = 'FormattedIp';
|
||||
export const FormattedIp = React.memo(FormattedIpComponent);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Port renders correctly against snapshot 1`] = `
|
||||
<FormattedFieldValue
|
||||
<Memo(FormattedFieldValueComponent)
|
||||
contextId="test"
|
||||
data-test-subj="formatted-ip"
|
||||
eventId="abcd"
|
||||
|
|
|
@ -50,6 +50,6 @@ describe('Port', () => {
|
|||
.find('a')
|
||||
.first()
|
||||
.props().href
|
||||
).toEqual('#/link-to/network/ip/10.1.2.3');
|
||||
).toEqual('#/link-to/network/ip/10.1.2.3/source');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -55,7 +55,7 @@ export const LinkToPage = React.memo<LinkToPageProps>(({ match }) => (
|
|||
/>
|
||||
<Route
|
||||
component={RedirectToNetworkPage}
|
||||
path={`${match.url}/:pageName(${SiemPageName.network})/ip/:detailName`}
|
||||
path={`${match.url}/:pageName(${SiemPageName.network})/ip/:detailName/:flowTarget`}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToDetectionEnginePage}
|
||||
|
|
|
@ -9,22 +9,24 @@ import { RouteComponentProps } from 'react-router-dom';
|
|||
|
||||
import { RedirectWrapper } from './redirect_wrapper';
|
||||
import { SiemPageName } from '../../pages/home/types';
|
||||
import { FlowTarget, FlowTargetSourceDest } from '../../graphql/types';
|
||||
|
||||
export type NetworkComponentProps = RouteComponentProps<{
|
||||
detailName: string;
|
||||
detailName?: string;
|
||||
flowTarget?: string;
|
||||
search: string;
|
||||
}>;
|
||||
|
||||
export const RedirectToNetworkPage = ({
|
||||
match: {
|
||||
params: { detailName },
|
||||
params: { detailName, flowTarget },
|
||||
},
|
||||
location: { search },
|
||||
}: NetworkComponentProps) => (
|
||||
<RedirectWrapper
|
||||
to={
|
||||
detailName
|
||||
? `/${SiemPageName.network}/ip/${detailName}${search}`
|
||||
? `/${SiemPageName.network}/ip/${detailName}/${flowTarget}${search}`
|
||||
: `/${SiemPageName.network}${search}`
|
||||
}
|
||||
/>
|
||||
|
@ -32,4 +34,7 @@ export const RedirectToNetworkPage = ({
|
|||
|
||||
const baseNetworkUrl = `#/link-to/${SiemPageName.network}`;
|
||||
export const getNetworkUrl = () => baseNetworkUrl;
|
||||
export const getIPDetailsUrl = (detailName: string) => `${baseNetworkUrl}/ip/${detailName}`;
|
||||
export const getIPDetailsUrl = (
|
||||
detailName: string,
|
||||
flowTarget?: FlowTarget | FlowTargetSourceDest
|
||||
) => `${baseNetworkUrl}/ip/${detailName}/${flowTarget || FlowTarget.source}`;
|
||||
|
|
|
@ -50,7 +50,7 @@ describe('Custom Links', () => {
|
|||
test('should render valid link to IP Details with ipv4 as the display text', () => {
|
||||
const wrapper = mount(<IPDetailsLink ip={ipv4} />);
|
||||
expect(wrapper.find('EuiLink').prop('href')).toEqual(
|
||||
`#/link-to/network/ip/${encodeURIComponent(ipv4)}`
|
||||
`#/link-to/network/ip/${encodeURIComponent(ipv4)}/source`
|
||||
);
|
||||
expect(wrapper.text()).toEqual(ipv4);
|
||||
});
|
||||
|
@ -58,7 +58,7 @@ describe('Custom Links', () => {
|
|||
test('should render valid link to IP Details with child text as the display text', () => {
|
||||
const wrapper = mount(<IPDetailsLink ip={ipv4}>{hostName}</IPDetailsLink>);
|
||||
expect(wrapper.find('EuiLink').prop('href')).toEqual(
|
||||
`#/link-to/network/ip/${encodeURIComponent(ipv4)}`
|
||||
`#/link-to/network/ip/${encodeURIComponent(ipv4)}/source`
|
||||
);
|
||||
expect(wrapper.text()).toEqual(hostName);
|
||||
});
|
||||
|
@ -66,7 +66,7 @@ describe('Custom Links', () => {
|
|||
test('should render valid link to IP Details with ipv6 as the display text', () => {
|
||||
const wrapper = mount(<IPDetailsLink ip={ipv6} />);
|
||||
expect(wrapper.find('EuiLink').prop('href')).toEqual(
|
||||
`#/link-to/network/ip/${encodeURIComponent(ipv6Encoded)}`
|
||||
`#/link-to/network/ip/${encodeURIComponent(ipv6Encoded)}/source`
|
||||
);
|
||||
expect(wrapper.text()).toEqual(ipv6);
|
||||
});
|
||||
|
|
|
@ -9,27 +9,31 @@ import React from 'react';
|
|||
|
||||
import { encodeIpv6 } from '../../lib/helpers';
|
||||
import { getHostDetailsUrl, getIPDetailsUrl } from '../link_to';
|
||||
import { FlowTarget, FlowTargetSourceDest } from '../../graphql/types';
|
||||
|
||||
// Internal Links
|
||||
export const HostDetailsLink = React.memo<{ children?: React.ReactNode; hostName: string }>(
|
||||
({ children, hostName }) => (
|
||||
<EuiLink href={getHostDetailsUrl(encodeURIComponent(hostName))}>
|
||||
{children ? children : hostName}
|
||||
</EuiLink>
|
||||
)
|
||||
const HostDetailsLinkComponent: React.FC<{ children?: React.ReactNode; hostName: string }> = ({
|
||||
children,
|
||||
hostName,
|
||||
}) => (
|
||||
<EuiLink href={getHostDetailsUrl(encodeURIComponent(hostName))}>
|
||||
{children ? children : hostName}
|
||||
</EuiLink>
|
||||
);
|
||||
|
||||
HostDetailsLink.displayName = 'HostDetailsLink';
|
||||
export const HostDetailsLink = React.memo(HostDetailsLinkComponent);
|
||||
|
||||
export const IPDetailsLink = React.memo<{ children?: React.ReactNode; ip: string }>(
|
||||
({ children, ip }) => (
|
||||
<EuiLink href={`${getIPDetailsUrl(encodeURIComponent(encodeIpv6(ip)))}`}>
|
||||
{children ? children : ip}
|
||||
</EuiLink>
|
||||
)
|
||||
const IPDetailsLinkComponent: React.FC<{
|
||||
children?: React.ReactNode;
|
||||
ip: string;
|
||||
flowTarget?: FlowTarget | FlowTargetSourceDest;
|
||||
}> = ({ children, ip, flowTarget = FlowTarget.source }) => (
|
||||
<EuiLink href={`${getIPDetailsUrl(encodeURIComponent(encodeIpv6(ip)), flowTarget)}`}>
|
||||
{children ? children : ip}
|
||||
</EuiLink>
|
||||
);
|
||||
|
||||
IPDetailsLink.displayName = 'IPDetailsLink';
|
||||
export const IPDetailsLink = React.memo(IPDetailsLinkComponent);
|
||||
|
||||
// External Links
|
||||
export const GoogleLink = React.memo<{ children?: React.ReactNode; link: string }>(
|
||||
|
|
|
@ -29,65 +29,69 @@ const sorting = {
|
|||
},
|
||||
} as const;
|
||||
|
||||
export const AnomaliesHostTable = React.memo<AnomaliesHostTableProps>(
|
||||
({ startDate, endDate, narrowDateRange, hostName, skip, type }): JSX.Element | null => {
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const [loading, tableData] = useAnomaliesTableData({
|
||||
startDate,
|
||||
endDate,
|
||||
skip,
|
||||
criteriaFields: getCriteriaFromHostType(type, hostName),
|
||||
});
|
||||
const AnomaliesHostTableComponent: React.FC<AnomaliesHostTableProps> = ({
|
||||
startDate,
|
||||
endDate,
|
||||
narrowDateRange,
|
||||
hostName,
|
||||
skip,
|
||||
type,
|
||||
}) => {
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const [loading, tableData] = useAnomaliesTableData({
|
||||
startDate,
|
||||
endDate,
|
||||
skip,
|
||||
criteriaFields: getCriteriaFromHostType(type, hostName),
|
||||
});
|
||||
|
||||
const hosts = convertAnomaliesToHosts(tableData, hostName);
|
||||
const hosts = convertAnomaliesToHosts(tableData, hostName);
|
||||
|
||||
const interval = getIntervalFromAnomalies(tableData);
|
||||
const columns = getAnomaliesHostTableColumnsCurated(
|
||||
type,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
const interval = getIntervalFromAnomalies(tableData);
|
||||
const columns = getAnomaliesHostTableColumnsCurated(
|
||||
type,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
const pagination = {
|
||||
initialPageIndex: 0,
|
||||
initialPageSize: 10,
|
||||
totalItemCount: hosts.length,
|
||||
pageSizeOptions: [5, 10, 20, 50],
|
||||
hidePerPageOptions: false,
|
||||
};
|
||||
|
||||
if (!hasMlUserPermissions(capabilities)) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Panel loading={loading}>
|
||||
<HeaderSection
|
||||
subtitle={`${i18n.SHOWING}: ${pagination.totalItemCount.toLocaleString()} ${i18n.UNIT(
|
||||
pagination.totalItemCount
|
||||
)}`}
|
||||
title={i18n.ANOMALIES}
|
||||
tooltip={i18n.TOOLTIP}
|
||||
/>
|
||||
|
||||
<BasicTable
|
||||
// @ts-ignore the Columns<T, U> type is not as specific as EUI's...
|
||||
columns={columns}
|
||||
compressed
|
||||
// @ts-ignore ...which leads to `networks` not "matching" the columns
|
||||
items={hosts}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
/>
|
||||
|
||||
{loading && (
|
||||
<Loader data-test-subj="anomalies-host-table-loading-panel" overlay size="xl" />
|
||||
)}
|
||||
</Panel>
|
||||
);
|
||||
const pagination = {
|
||||
initialPageIndex: 0,
|
||||
initialPageSize: 10,
|
||||
totalItemCount: hosts.length,
|
||||
pageSizeOptions: [5, 10, 20, 50],
|
||||
hidePerPageOptions: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
if (!hasMlUserPermissions(capabilities)) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Panel loading={loading}>
|
||||
<HeaderSection
|
||||
subtitle={`${i18n.SHOWING}: ${pagination.totalItemCount.toLocaleString()} ${i18n.UNIT(
|
||||
pagination.totalItemCount
|
||||
)}`}
|
||||
title={i18n.ANOMALIES}
|
||||
tooltip={i18n.TOOLTIP}
|
||||
/>
|
||||
|
||||
<BasicTable
|
||||
// @ts-ignore the Columns<T, U> type is not as specific as EUI's...
|
||||
columns={columns}
|
||||
compressed
|
||||
// @ts-ignore ...which leads to `networks` not "matching" the columns
|
||||
items={hosts}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
/>
|
||||
|
||||
{loading && (
|
||||
<Loader data-test-subj="anomalies-host-table-loading-panel" overlay size="xl" />
|
||||
)}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
},
|
||||
hostEquality
|
||||
);
|
||||
|
||||
AnomaliesHostTable.displayName = 'AnomaliesHostTable';
|
||||
export const AnomaliesHostTable = React.memo(AnomaliesHostTableComponent, hostEquality);
|
||||
|
|
|
@ -13,7 +13,6 @@ import { convertAnomaliesToNetwork } from './convert_anomalies_to_network';
|
|||
import { Loader } from '../../loader';
|
||||
import { AnomaliesNetworkTableProps } from '../types';
|
||||
import { getAnomaliesNetworkTableColumnsCurated } from './get_anomalies_network_table_columns';
|
||||
import { getIntervalFromAnomalies } from '../anomaly/get_interval_from_anomalies';
|
||||
import { hasMlUserPermissions } from '../permissions/has_ml_user_permissions';
|
||||
import { MlCapabilitiesContext } from '../permissions/ml_capabilities_provider';
|
||||
import { BasicTable } from './basic_table';
|
||||
|
@ -28,64 +27,61 @@ const sorting = {
|
|||
},
|
||||
} as const;
|
||||
|
||||
export const AnomaliesNetworkTable = React.memo<AnomaliesNetworkTableProps>(
|
||||
({ startDate, endDate, narrowDateRange, skip, ip, type, flowTarget }): JSX.Element | null => {
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const [loading, tableData] = useAnomaliesTableData({
|
||||
startDate,
|
||||
endDate,
|
||||
skip,
|
||||
criteriaFields: getCriteriaFromNetworkType(type, ip, flowTarget),
|
||||
});
|
||||
const AnomaliesNetworkTableComponent: React.FC<AnomaliesNetworkTableProps> = ({
|
||||
startDate,
|
||||
endDate,
|
||||
skip,
|
||||
ip,
|
||||
type,
|
||||
flowTarget,
|
||||
}) => {
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const [loading, tableData] = useAnomaliesTableData({
|
||||
startDate,
|
||||
endDate,
|
||||
skip,
|
||||
criteriaFields: getCriteriaFromNetworkType(type, ip, flowTarget),
|
||||
});
|
||||
|
||||
const networks = convertAnomaliesToNetwork(tableData, ip);
|
||||
const interval = getIntervalFromAnomalies(tableData);
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(
|
||||
type,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
const networks = convertAnomaliesToNetwork(tableData, ip);
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(type, startDate, endDate, flowTarget);
|
||||
const pagination = {
|
||||
initialPageIndex: 0,
|
||||
initialPageSize: 10,
|
||||
totalItemCount: networks.length,
|
||||
pageSizeOptions: [5, 10, 20, 50],
|
||||
hidePerPageOptions: false,
|
||||
};
|
||||
|
||||
if (!hasMlUserPermissions(capabilities)) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Panel loading={loading}>
|
||||
<HeaderSection
|
||||
subtitle={`${i18n.SHOWING}: ${pagination.totalItemCount.toLocaleString()} ${i18n.UNIT(
|
||||
pagination.totalItemCount
|
||||
)}`}
|
||||
title={i18n.ANOMALIES}
|
||||
tooltip={i18n.TOOLTIP}
|
||||
/>
|
||||
|
||||
<BasicTable
|
||||
// @ts-ignore the Columns<T, U> type is not as specific as EUI's...
|
||||
columns={columns}
|
||||
compressed
|
||||
// @ts-ignore ...which leads to `networks` not "matching" the columns
|
||||
items={networks}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
/>
|
||||
|
||||
{loading && (
|
||||
<Loader data-test-subj="anomalies-network-table-loading-panel" overlay size="xl" />
|
||||
)}
|
||||
</Panel>
|
||||
);
|
||||
const pagination = {
|
||||
initialPageIndex: 0,
|
||||
initialPageSize: 10,
|
||||
totalItemCount: networks.length,
|
||||
pageSizeOptions: [5, 10, 20, 50],
|
||||
hidePerPageOptions: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
if (!hasMlUserPermissions(capabilities)) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Panel loading={loading}>
|
||||
<HeaderSection
|
||||
subtitle={`${i18n.SHOWING}: ${pagination.totalItemCount.toLocaleString()} ${i18n.UNIT(
|
||||
pagination.totalItemCount
|
||||
)}`}
|
||||
title={i18n.ANOMALIES}
|
||||
tooltip={i18n.TOOLTIP}
|
||||
/>
|
||||
|
||||
<BasicTable
|
||||
// @ts-ignore the Columns<T, U> type is not as specific as EUI's...
|
||||
columns={columns}
|
||||
compressed
|
||||
// @ts-ignore ...which leads to `networks` not "matching" the columns
|
||||
items={networks}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
/>
|
||||
|
||||
{loading && (
|
||||
<Loader data-test-subj="anomalies-network-table-loading-panel" overlay size="xl" />
|
||||
)}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
},
|
||||
networkEquality
|
||||
);
|
||||
|
||||
AnomaliesNetworkTable.displayName = 'AnomaliesNetworkTable';
|
||||
export const AnomaliesNetworkTable = React.memo(AnomaliesNetworkTableComponent, networkEquality);
|
||||
|
|
|
@ -15,65 +15,33 @@ import { useMountAppended } from '../../../utils/use_mount_appended';
|
|||
|
||||
const startDate = new Date(2001).valueOf();
|
||||
const endDate = new Date(3000).valueOf();
|
||||
const interval = 'days';
|
||||
const narrowDateRange = jest.fn();
|
||||
|
||||
describe('get_anomalies_network_table_columns', () => {
|
||||
const mount = useMountAppended();
|
||||
|
||||
test('on network page, we expect to get all columns', () => {
|
||||
expect(
|
||||
getAnomaliesNetworkTableColumnsCurated(
|
||||
NetworkType.page,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
).length
|
||||
getAnomaliesNetworkTableColumnsCurated(NetworkType.page, startDate, endDate).length
|
||||
).toEqual(6);
|
||||
});
|
||||
|
||||
test('on network details page, we expect to remove one columns', () => {
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(
|
||||
NetworkType.details,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.details, startDate, endDate);
|
||||
expect(columns.length).toEqual(5);
|
||||
});
|
||||
|
||||
test('on network page, we should have Network Name', () => {
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(
|
||||
NetworkType.page,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.page, startDate, endDate);
|
||||
expect(columns.some(col => col.name === i18n.NETWORK_NAME)).toEqual(true);
|
||||
});
|
||||
|
||||
test('on network details page, we should not have Network Name', () => {
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(
|
||||
NetworkType.details,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.details, startDate, endDate);
|
||||
expect(columns.some(col => col.name === i18n.NETWORK_NAME)).toEqual(false);
|
||||
});
|
||||
|
||||
test('on network page, we should escape the draggable id', () => {
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(
|
||||
NetworkType.page,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.page, startDate, endDate);
|
||||
const column = columns.find(col => col.name === i18n.SCORE) as Columns<
|
||||
string,
|
||||
AnomaliesByNetwork
|
||||
|
@ -129,13 +97,7 @@ describe('get_anomalies_network_table_columns', () => {
|
|||
});
|
||||
|
||||
test('on network page, undefined influencers should turn into an empty column string', () => {
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(
|
||||
NetworkType.page,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.page, startDate, endDate);
|
||||
const column = columns.find(col => col.name === i18n.INFLUENCED_BY) as Columns<
|
||||
Anomaly['influencers'],
|
||||
AnomaliesByNetwork
|
||||
|
|
|
@ -10,7 +10,7 @@ import React from 'react';
|
|||
import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
|
||||
|
||||
import { Columns } from '../../paginated_table';
|
||||
import { Anomaly, NarrowDateRange, AnomaliesByNetwork } from '../types';
|
||||
import { Anomaly, AnomaliesByNetwork } from '../types';
|
||||
import { getRowItemDraggable } from '../../tables/helpers';
|
||||
import { EntityDraggable } from '../entity_draggable';
|
||||
import { createCompoundNetworkKey } from './create_compound_key';
|
||||
|
@ -23,12 +23,12 @@ import { createExplorerLink } from '../links/create_explorer_link';
|
|||
import { FormattedRelativePreferenceDate } from '../../formatted_date';
|
||||
import { NetworkType } from '../../../store/network/model';
|
||||
import { escapeDataProviderId } from '../../drag_and_drop/helpers';
|
||||
import { FlowTarget } from '../../../graphql/types';
|
||||
|
||||
export const getAnomaliesNetworkTableColumns = (
|
||||
startDate: number,
|
||||
endDate: number,
|
||||
interval: string,
|
||||
narrowDateRange: NarrowDateRange
|
||||
flowTarget?: FlowTarget
|
||||
): [
|
||||
Columns<AnomaliesByNetwork['ip'], AnomaliesByNetwork>,
|
||||
Columns<Anomaly['severity'], AnomaliesByNetwork>,
|
||||
|
@ -46,7 +46,7 @@ export const getAnomaliesNetworkTableColumns = (
|
|||
rowItem: ip,
|
||||
attrName: anomaliesByNetwork.type,
|
||||
idPrefix: `anomalies-network-table-ip-${createCompoundNetworkKey(anomaliesByNetwork)}`,
|
||||
render: item => <IPDetailsLink ip={item} />,
|
||||
render: item => <IPDetailsLink ip={item} flowTarget={flowTarget} />,
|
||||
}),
|
||||
},
|
||||
{
|
||||
|
@ -129,10 +129,9 @@ export const getAnomaliesNetworkTableColumnsCurated = (
|
|||
pageType: NetworkType,
|
||||
startDate: number,
|
||||
endDate: number,
|
||||
interval: string,
|
||||
narrowDateRange: NarrowDateRange
|
||||
flowTarget?: FlowTarget
|
||||
) => {
|
||||
const columns = getAnomaliesNetworkTableColumns(startDate, endDate, interval, narrowDateRange);
|
||||
const columns = getAnomaliesNetworkTableColumns(startDate, endDate, flowTarget);
|
||||
|
||||
// Columns to exclude from ip details pages
|
||||
if (pageType === NetworkType.details) {
|
||||
|
|
|
@ -194,7 +194,7 @@ describe('Navigation Breadcrumbs', () => {
|
|||
},
|
||||
{
|
||||
text: ipv4,
|
||||
href: `#/link-to/network/ip/${ipv4}?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`,
|
||||
href: `#/link-to/network/ip/${ipv4}/source?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`,
|
||||
},
|
||||
{ text: 'Flows', href: '' },
|
||||
]);
|
||||
|
@ -211,7 +211,7 @@ describe('Navigation Breadcrumbs', () => {
|
|||
},
|
||||
{
|
||||
text: ipv6,
|
||||
href: `#/link-to/network/ip/${ipv6Encoded}?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`,
|
||||
href: `#/link-to/network/ip/${ipv6Encoded}/source?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`,
|
||||
},
|
||||
{ text: 'Flows', href: '' },
|
||||
]);
|
||||
|
|
|
@ -19,7 +19,7 @@ import { SiemNavigationProps, SiemNavigationComponentProps } from './types';
|
|||
export const SiemNavigationComponent = React.memo<
|
||||
SiemNavigationComponentProps & SiemNavigationProps & RouteSpyState
|
||||
>(
|
||||
({ detailName, display, navTabs, pageName, pathName, search, tabName, urlState }) => {
|
||||
({ detailName, display, navTabs, pageName, pathName, search, tabName, urlState, flowTarget }) => {
|
||||
useEffect(() => {
|
||||
if (pathName) {
|
||||
setBreadcrumbs({
|
||||
|
@ -32,6 +32,7 @@ export const SiemNavigationComponent = React.memo<
|
|||
savedQuery: urlState.savedQuery,
|
||||
search,
|
||||
tabName,
|
||||
flowTarget,
|
||||
timerange: urlState.timerange,
|
||||
timeline: urlState.timeline,
|
||||
});
|
||||
|
|
|
@ -7,38 +7,26 @@
|
|||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { apolloClientObservable, mockGlobalState, TestProviders } from '../../../../mock';
|
||||
import { createStore, State } from '../../../../store';
|
||||
|
||||
import { TestProviders } from '../../../../mock';
|
||||
import { FlowTargetSelectConnected } from './index';
|
||||
import { IpOverviewId } from '../../../field_renderers/field_renderers';
|
||||
import { FlowTarget } from '../../../../graphql/types';
|
||||
|
||||
describe('Flow Target Select Connected', () => {
|
||||
const state: State = mockGlobalState;
|
||||
let store = createStore(state, apolloClientObservable);
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, apolloClientObservable);
|
||||
});
|
||||
test('Pick Relative Date', () => {
|
||||
describe.skip('Flow Target Select Connected', () => {
|
||||
test('renders correctly against snapshot flowTarget source', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders store={store}>
|
||||
<FlowTargetSelectConnected />
|
||||
<TestProviders>
|
||||
<FlowTargetSelectConnected flowTarget={FlowTarget.source} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(store.getState().network.details.flowTarget).toEqual('source');
|
||||
wrapper
|
||||
.find('button')
|
||||
.first()
|
||||
.simulate('click');
|
||||
expect(wrapper.find('FlowTargetSelectConnected')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
wrapper.update();
|
||||
wrapper
|
||||
.find(`button#${IpOverviewId}-select-flow-target-destination`)
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
expect(store.getState().network.details.flowTarget).toEqual('destination');
|
||||
test('renders correctly against snapshot flowTarget destination', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FlowTargetSelectConnected flowTarget={FlowTarget.destination} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find('FlowTargetSelectConnected')).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,15 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Location } from 'history';
|
||||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
|
||||
import { FlowDirection, FlowTarget } from '../../../../graphql/types';
|
||||
import { State } from '../../../../store';
|
||||
import { networkActions, networkSelectors } from '../../../../store/network';
|
||||
import * as i18nIp from '../ip_overview/translations';
|
||||
|
||||
import { FlowTargetSelect } from '../../../flow_controls/flow_target_select';
|
||||
|
@ -24,20 +22,33 @@ const SelectTypeItem = styled(EuiFlexItem)`
|
|||
|
||||
SelectTypeItem.displayName = 'SelectTypeItem';
|
||||
|
||||
interface FlowTargetSelectReduxProps {
|
||||
interface Props {
|
||||
flowTarget: FlowTarget;
|
||||
}
|
||||
|
||||
export interface FlowTargetSelectDispatchProps {
|
||||
updateIpDetailsFlowTarget: ActionCreator<{
|
||||
flowTarget: FlowTarget;
|
||||
}>;
|
||||
}
|
||||
const getUpdatedFlowTargetPath = (
|
||||
location: Location,
|
||||
currentFlowTarget: FlowTarget,
|
||||
newFlowTarget: FlowTarget
|
||||
) => {
|
||||
const newPathame = location.pathname.replace(currentFlowTarget, newFlowTarget);
|
||||
|
||||
type FlowTargetSelectProps = FlowTargetSelectReduxProps & FlowTargetSelectDispatchProps;
|
||||
return `${newPathame}${location.search}`;
|
||||
};
|
||||
|
||||
const FlowTargetSelectComponent = React.memo<FlowTargetSelectProps>(
|
||||
({ flowTarget, updateIpDetailsFlowTarget }) => (
|
||||
const FlowTargetSelectConnectedComponent: React.FC<Props> = ({ flowTarget }) => {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
|
||||
const updateIpDetailsFlowTarget = useCallback(
|
||||
(newFlowTarget: FlowTarget) => {
|
||||
const newPath = getUpdatedFlowTargetPath(location, flowTarget, newFlowTarget);
|
||||
history.push(newPath);
|
||||
},
|
||||
[history, location, flowTarget]
|
||||
);
|
||||
|
||||
return (
|
||||
<SelectTypeItem grow={false} data-test-subj={`${IpOverviewId}-select-flow-target`}>
|
||||
<FlowTargetSelect
|
||||
id={IpOverviewId}
|
||||
|
@ -48,20 +59,7 @@ const FlowTargetSelectComponent = React.memo<FlowTargetSelectProps>(
|
|||
updateFlowTargetAction={updateIpDetailsFlowTarget}
|
||||
/>
|
||||
</SelectTypeItem>
|
||||
)
|
||||
);
|
||||
|
||||
FlowTargetSelectComponent.displayName = 'FlowTargetSelectComponent';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getIpDetailsFlowTargetSelector = networkSelectors.ipDetailsFlowTargetSelector();
|
||||
return (state: State) => {
|
||||
return {
|
||||
flowTarget: getIpDetailsFlowTargetSelector(state),
|
||||
};
|
||||
};
|
||||
);
|
||||
};
|
||||
|
||||
export const FlowTargetSelectConnected = connect(makeMapStateToProps, {
|
||||
updateIpDetailsFlowTarget: networkActions.updateIpDetailsFlowTarget,
|
||||
})(FlowTargetSelectComponent);
|
||||
export const FlowTargetSelectConnected = React.memo(FlowTargetSelectConnectedComponent);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NetworkTopNFlow Table Component rendering it renders the default NetworkTopNFlow table on the IP Details page 1`] = `
|
||||
<Connect(NetworkTopNFlowTableComponent)
|
||||
<Connect(Component)
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
|
@ -100,97 +100,6 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ
|
|||
fakeTotalCount={50}
|
||||
flowTargeted="source"
|
||||
id="topNFlowSource"
|
||||
indexPattern={
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "@timestamp",
|
||||
"searchable": true,
|
||||
"type": "date",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "@version",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.ephemeral_id",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.hostname",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.id",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test1",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test2",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test3",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test4",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test5",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test6",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test7",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test8",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "host.name",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
],
|
||||
"title": "filebeat-*,auditbeat-*,packetbeat-*",
|
||||
}
|
||||
}
|
||||
isInspect={false}
|
||||
loadPage={[MockFunction]}
|
||||
loading={false}
|
||||
|
@ -201,7 +110,7 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ
|
|||
`;
|
||||
|
||||
exports[`NetworkTopNFlow Table Component rendering it renders the default NetworkTopNFlow table on the Network page 1`] = `
|
||||
<Connect(NetworkTopNFlowTableComponent)
|
||||
<Connect(Component)
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
|
@ -300,97 +209,6 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ
|
|||
fakeTotalCount={50}
|
||||
flowTargeted="source"
|
||||
id="topNFlowSource"
|
||||
indexPattern={
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "@timestamp",
|
||||
"searchable": true,
|
||||
"type": "date",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "@version",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.ephemeral_id",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.hostname",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.id",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test1",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test2",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test3",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test4",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test5",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test6",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test7",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "agent.test8",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "host.name",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
],
|
||||
"title": "filebeat-*,auditbeat-*,packetbeat-*",
|
||||
}
|
||||
}
|
||||
isInspect={false}
|
||||
loadPage={[MockFunction]}
|
||||
loading={false}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import { get } from 'lodash/fp';
|
||||
import numeral from '@elastic/numeral';
|
||||
import React from 'react';
|
||||
import { IIndexPattern } from 'src/plugins/data/public';
|
||||
|
||||
import { CountryFlag } from '../../../source_destination/country_flag';
|
||||
import {
|
||||
|
@ -48,9 +47,7 @@ export type NetworkTopNFlowColumnsIpDetails = [
|
|||
];
|
||||
|
||||
export const getNetworkTopNFlowColumns = (
|
||||
indexPattern: IIndexPattern,
|
||||
flowTarget: FlowTargetSourceDest,
|
||||
type: networkModel.NetworkType,
|
||||
tableId: string
|
||||
): NetworkTopNFlowColumns => [
|
||||
{
|
||||
|
@ -83,7 +80,7 @@ export const getNetworkTopNFlowColumns = (
|
|||
<Provider dataProvider={dataProvider} />
|
||||
</DragEffects>
|
||||
) : (
|
||||
<IPDetailsLink ip={ip} />
|
||||
<IPDetailsLink ip={ip} flowTarget={flowTarget} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
@ -233,12 +230,11 @@ export const getNetworkTopNFlowColumns = (
|
|||
];
|
||||
|
||||
export const getNFlowColumnsCurated = (
|
||||
indexPattern: IIndexPattern,
|
||||
flowTarget: FlowTargetSourceDest,
|
||||
type: networkModel.NetworkType,
|
||||
tableId: string
|
||||
): NetworkTopNFlowColumns | NetworkTopNFlowColumnsIpDetails => {
|
||||
const columns = getNetworkTopNFlowColumns(indexPattern, flowTarget, type, tableId);
|
||||
const columns = getNetworkTopNFlowColumns(flowTarget, tableId);
|
||||
|
||||
// Columns to exclude from host details pages
|
||||
if (type === networkModel.NetworkType.details) {
|
||||
|
|
|
@ -11,12 +11,7 @@ import { MockedProvider } from 'react-apollo/test-utils';
|
|||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
|
||||
import { FlowTargetSourceDest } from '../../../../graphql/types';
|
||||
import {
|
||||
apolloClientObservable,
|
||||
mockGlobalState,
|
||||
mockIndexPattern,
|
||||
TestProviders,
|
||||
} from '../../../../mock';
|
||||
import { apolloClientObservable, mockGlobalState, TestProviders } from '../../../../mock';
|
||||
import { useMountAppended } from '../../../../utils/use_mount_appended';
|
||||
import { createStore, networkModel, State } from '../../../../store';
|
||||
|
||||
|
@ -43,7 +38,6 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.NetworkTopNFlow.pageInfo)}
|
||||
flowTargeted={FlowTargetSourceDest.source}
|
||||
id="topNFlowSource"
|
||||
indexPattern={mockIndexPattern}
|
||||
isInspect={false}
|
||||
loading={false}
|
||||
loadPage={loadPage}
|
||||
|
@ -58,7 +52,7 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
</ReduxStoreProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('Connect(NetworkTopNFlowTableComponent)')).toMatchSnapshot();
|
||||
expect(wrapper.find('Connect(Component)')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it renders the default NetworkTopNFlow table on the IP Details page', () => {
|
||||
|
@ -69,7 +63,6 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.NetworkTopNFlow.pageInfo)}
|
||||
flowTargeted={FlowTargetSourceDest.source}
|
||||
id="topNFlowSource"
|
||||
indexPattern={mockIndexPattern}
|
||||
isInspect={false}
|
||||
loading={false}
|
||||
loadPage={loadPage}
|
||||
|
@ -84,7 +77,7 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
</ReduxStoreProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('Connect(NetworkTopNFlowTableComponent)')).toMatchSnapshot();
|
||||
expect(wrapper.find('Connect(Component)')).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -99,7 +92,6 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
flowTargeted={FlowTargetSourceDest.source}
|
||||
id="topNFlowSource"
|
||||
isInspect={false}
|
||||
indexPattern={mockIndexPattern}
|
||||
loading={false}
|
||||
loadPage={loadPage}
|
||||
showMorePagesIndicator={getOr(
|
||||
|
|
|
@ -8,7 +8,6 @@ import React, { useCallback, useMemo } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { compose } from 'redux';
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
import { IIndexPattern } from 'src/plugins/data/public';
|
||||
|
||||
import { networkActions } from '../../../../store/actions';
|
||||
import {
|
||||
|
@ -29,7 +28,6 @@ interface OwnProps {
|
|||
fakeTotalCount: number;
|
||||
flowTargeted: FlowTargetSourceDest;
|
||||
id: string;
|
||||
indexPattern: IIndexPattern;
|
||||
isInspect: boolean;
|
||||
loading: boolean;
|
||||
loadPage: (newActivePage: number) => void;
|
||||
|
@ -69,118 +67,112 @@ const rowItems: ItemsPerRow[] = [
|
|||
|
||||
export const NetworkTopNFlowTableId = 'networkTopSourceFlow-top-talkers';
|
||||
|
||||
const NetworkTopNFlowTableComponent = React.memo<NetworkTopNFlowTableProps>(
|
||||
({
|
||||
activePage,
|
||||
data,
|
||||
fakeTotalCount,
|
||||
flowTargeted,
|
||||
id,
|
||||
indexPattern,
|
||||
isInspect,
|
||||
limit,
|
||||
loading,
|
||||
loadPage,
|
||||
showMorePagesIndicator,
|
||||
sort,
|
||||
totalCount,
|
||||
type,
|
||||
updateNetworkTable,
|
||||
}) => {
|
||||
const columns = useMemo(
|
||||
() => getNFlowColumnsCurated(indexPattern, flowTargeted, type, NetworkTopNFlowTableId),
|
||||
[indexPattern, flowTargeted, type]
|
||||
);
|
||||
const NetworkTopNFlowTableComponent: React.FC<NetworkTopNFlowTableProps> = ({
|
||||
activePage,
|
||||
data,
|
||||
fakeTotalCount,
|
||||
flowTargeted,
|
||||
id,
|
||||
isInspect,
|
||||
limit,
|
||||
loading,
|
||||
loadPage,
|
||||
showMorePagesIndicator,
|
||||
sort,
|
||||
totalCount,
|
||||
type,
|
||||
updateNetworkTable,
|
||||
}) => {
|
||||
const columns = useMemo(
|
||||
() => getNFlowColumnsCurated(flowTargeted, type, NetworkTopNFlowTableId),
|
||||
[flowTargeted, type]
|
||||
);
|
||||
|
||||
let tableType: networkModel.TopNTableType;
|
||||
const headerTitle: string =
|
||||
flowTargeted === FlowTargetSourceDest.source ? i18n.SOURCE_IP : i18n.DESTINATION_IP;
|
||||
let tableType: networkModel.TopNTableType;
|
||||
const headerTitle: string =
|
||||
flowTargeted === FlowTargetSourceDest.source ? i18n.SOURCE_IP : i18n.DESTINATION_IP;
|
||||
|
||||
if (type === networkModel.NetworkType.page) {
|
||||
tableType =
|
||||
flowTargeted === FlowTargetSourceDest.source
|
||||
? networkModel.NetworkTableType.topNFlowSource
|
||||
: networkModel.NetworkTableType.topNFlowDestination;
|
||||
} else {
|
||||
tableType =
|
||||
flowTargeted === FlowTargetSourceDest.source
|
||||
? networkModel.IpDetailsTableType.topNFlowSource
|
||||
: networkModel.IpDetailsTableType.topNFlowDestination;
|
||||
}
|
||||
|
||||
const onChange = useCallback(
|
||||
(criteria: Criteria) => {
|
||||
if (criteria.sort != null) {
|
||||
const splitField = criteria.sort.field.split('.');
|
||||
const field = last(splitField);
|
||||
const newSortDirection = field !== sort.field ? Direction.desc : criteria.sort.direction; // sort by desc on init click
|
||||
const newTopNFlowSort: NetworkTopTablesSortField = {
|
||||
field: field as NetworkTopTablesFields,
|
||||
direction: newSortDirection as Direction,
|
||||
};
|
||||
if (!isEqual(newTopNFlowSort, sort)) {
|
||||
updateNetworkTable({
|
||||
networkType: type,
|
||||
tableType,
|
||||
updates: {
|
||||
sort: newTopNFlowSort,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[sort, type, tableType, updateNetworkTable]
|
||||
);
|
||||
|
||||
const field =
|
||||
sort.field === NetworkTopTablesFields.bytes_out ||
|
||||
sort.field === NetworkTopTablesFields.bytes_in
|
||||
? `node.network.${sort.field}`
|
||||
: `node.${flowTargeted}.${sort.field}`;
|
||||
|
||||
const updateActivePage = useCallback(
|
||||
newPage =>
|
||||
updateNetworkTable({
|
||||
networkType: type,
|
||||
tableType,
|
||||
updates: { activePage: newPage },
|
||||
}),
|
||||
[updateNetworkTable, type, tableType]
|
||||
);
|
||||
|
||||
const updateLimitPagination = useCallback(
|
||||
newLimit =>
|
||||
updateNetworkTable({ networkType: type, tableType, updates: { limit: newLimit } }),
|
||||
[updateNetworkTable, type, tableType]
|
||||
);
|
||||
|
||||
return (
|
||||
<PaginatedTable
|
||||
activePage={activePage}
|
||||
columns={columns}
|
||||
dataTestSubj={`table-${tableType}`}
|
||||
headerCount={totalCount}
|
||||
headerTitle={headerTitle}
|
||||
headerUnit={i18n.UNIT(totalCount)}
|
||||
id={id}
|
||||
isInspect={isInspect}
|
||||
itemsPerRow={rowItems}
|
||||
limit={limit}
|
||||
loading={loading}
|
||||
loadPage={loadPage}
|
||||
onChange={onChange}
|
||||
pageOfItems={data}
|
||||
showMorePagesIndicator={showMorePagesIndicator}
|
||||
sorting={{ field, direction: sort.direction }}
|
||||
totalCount={fakeTotalCount}
|
||||
updateActivePage={updateActivePage}
|
||||
updateLimitPagination={updateLimitPagination}
|
||||
/>
|
||||
);
|
||||
if (type === networkModel.NetworkType.page) {
|
||||
tableType =
|
||||
flowTargeted === FlowTargetSourceDest.source
|
||||
? networkModel.NetworkTableType.topNFlowSource
|
||||
: networkModel.NetworkTableType.topNFlowDestination;
|
||||
} else {
|
||||
tableType =
|
||||
flowTargeted === FlowTargetSourceDest.source
|
||||
? networkModel.IpDetailsTableType.topNFlowSource
|
||||
: networkModel.IpDetailsTableType.topNFlowDestination;
|
||||
}
|
||||
);
|
||||
|
||||
NetworkTopNFlowTableComponent.displayName = 'NetworkTopNFlowTableComponent';
|
||||
const onChange = useCallback(
|
||||
(criteria: Criteria) => {
|
||||
if (criteria.sort != null) {
|
||||
const splitField = criteria.sort.field.split('.');
|
||||
const field = last(splitField);
|
||||
const newSortDirection = field !== sort.field ? Direction.desc : criteria.sort.direction; // sort by desc on init click
|
||||
const newTopNFlowSort: NetworkTopTablesSortField = {
|
||||
field: field as NetworkTopTablesFields,
|
||||
direction: newSortDirection as Direction,
|
||||
};
|
||||
if (!isEqual(newTopNFlowSort, sort)) {
|
||||
updateNetworkTable({
|
||||
networkType: type,
|
||||
tableType,
|
||||
updates: {
|
||||
sort: newTopNFlowSort,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[sort, type, tableType, updateNetworkTable]
|
||||
);
|
||||
|
||||
const field =
|
||||
sort.field === NetworkTopTablesFields.bytes_out ||
|
||||
sort.field === NetworkTopTablesFields.bytes_in
|
||||
? `node.network.${sort.field}`
|
||||
: `node.${flowTargeted}.${sort.field}`;
|
||||
|
||||
const updateActivePage = useCallback(
|
||||
newPage =>
|
||||
updateNetworkTable({
|
||||
networkType: type,
|
||||
tableType,
|
||||
updates: { activePage: newPage },
|
||||
}),
|
||||
[updateNetworkTable, type, tableType]
|
||||
);
|
||||
|
||||
const updateLimitPagination = useCallback(
|
||||
newLimit => updateNetworkTable({ networkType: type, tableType, updates: { limit: newLimit } }),
|
||||
[updateNetworkTable, type, tableType]
|
||||
);
|
||||
|
||||
return (
|
||||
<PaginatedTable
|
||||
activePage={activePage}
|
||||
columns={columns}
|
||||
dataTestSubj={`table-${tableType}`}
|
||||
headerCount={totalCount}
|
||||
headerTitle={headerTitle}
|
||||
headerUnit={i18n.UNIT(totalCount)}
|
||||
id={id}
|
||||
isInspect={isInspect}
|
||||
itemsPerRow={rowItems}
|
||||
limit={limit}
|
||||
loading={loading}
|
||||
loadPage={loadPage}
|
||||
onChange={onChange}
|
||||
pageOfItems={data}
|
||||
showMorePagesIndicator={showMorePagesIndicator}
|
||||
sorting={{ field, direction: sort.direction }}
|
||||
totalCount={fakeTotalCount}
|
||||
updateActivePage={updateActivePage}
|
||||
updateLimitPagination={updateLimitPagination}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getTopNFlowSelector = networkSelectors.topNFlowSelector();
|
||||
|
@ -192,4 +184,4 @@ export const NetworkTopNFlowTable = compose<React.ComponentClass<OwnProps>>(
|
|||
connect(makeMapStateToProps, {
|
||||
updateNetworkTable: networkActions.updateNetworkTable,
|
||||
})
|
||||
)(NetworkTopNFlowTableComponent);
|
||||
)(React.memo(NetworkTopNFlowTableComponent));
|
||||
|
|
|
@ -49,7 +49,7 @@ exports[`Columns it renders the expected columns 1`] = `
|
|||
<styled.div
|
||||
data-test-subj="cell-container"
|
||||
>
|
||||
<FormattedFieldValue
|
||||
<Memo(FormattedFieldValueComponent)
|
||||
contextId="plain-column-renderer-formatted-field-value-test"
|
||||
eventId="1"
|
||||
fieldFormat=""
|
||||
|
@ -72,7 +72,7 @@ exports[`Columns it renders the expected columns 1`] = `
|
|||
<styled.div
|
||||
data-test-subj="cell-container"
|
||||
>
|
||||
<FormattedFieldValue
|
||||
<Memo(FormattedFieldValueComponent)
|
||||
contextId="plain-column-renderer-formatted-field-value-test"
|
||||
eventId="1"
|
||||
fieldFormat=""
|
||||
|
@ -95,7 +95,7 @@ exports[`Columns it renders the expected columns 1`] = `
|
|||
<styled.div
|
||||
data-test-subj="cell-container"
|
||||
>
|
||||
<FormattedFieldValue
|
||||
<Memo(FormattedFieldValueComponent)
|
||||
contextId="plain-column-renderer-formatted-field-value-test"
|
||||
eventId="1"
|
||||
fieldFormat=""
|
||||
|
@ -118,7 +118,7 @@ exports[`Columns it renders the expected columns 1`] = `
|
|||
<styled.div
|
||||
data-test-subj="cell-container"
|
||||
>
|
||||
<FormattedFieldValue
|
||||
<Memo(FormattedFieldValueComponent)
|
||||
contextId="plain-column-renderer-formatted-field-value-test"
|
||||
eventId="1"
|
||||
fieldFormat=""
|
||||
|
@ -141,7 +141,7 @@ exports[`Columns it renders the expected columns 1`] = `
|
|||
<styled.div
|
||||
data-test-subj="cell-container"
|
||||
>
|
||||
<FormattedFieldValue
|
||||
<Memo(FormattedFieldValueComponent)
|
||||
contextId="plain-column-renderer-formatted-field-value-test"
|
||||
eventId="1"
|
||||
fieldFormat=""
|
||||
|
@ -164,7 +164,7 @@ exports[`Columns it renders the expected columns 1`] = `
|
|||
<styled.div
|
||||
data-test-subj="cell-container"
|
||||
>
|
||||
<FormattedFieldValue
|
||||
<Memo(FormattedFieldValueComponent)
|
||||
contextId="plain-column-renderer-formatted-field-value-test"
|
||||
eventId="1"
|
||||
fieldFormat=""
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Events renders correctly against snapshot 1`] = `
|
||||
<FormattedFieldValue
|
||||
contextId="test"
|
||||
eventId="1"
|
||||
fieldName="timestamp"
|
||||
fieldType="date"
|
||||
value="2018-11-05T19:03:25.937Z"
|
||||
/>
|
||||
`;
|
||||
exports[`Events renders correctly against snapshot 1`] = `null`;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`get_column_renderer renders correctly against snapshot 1`] = `
|
||||
<span>
|
||||
<FormattedFieldValue
|
||||
<Memo(FormattedFieldValueComponent)
|
||||
contextId="plain-column-renderer-formatted-field-value-test"
|
||||
eventId="1"
|
||||
fieldFormat=""
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`plain_column_renderer rendering renders correctly against snapshot 1`] = `
|
||||
<span>
|
||||
<FormattedFieldValue
|
||||
<Memo(FormattedFieldValueComponent)
|
||||
contextId="plain-column-renderer-formatted-field-value-test"
|
||||
eventId="1"
|
||||
fieldFormat=""
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
// simple black-list to prevent dragging and dropping fields such as message name
|
||||
const columnNamesNotDraggable = [MESSAGE_FIELD_NAME];
|
||||
|
||||
export const FormattedFieldValue = React.memo<{
|
||||
const FormattedFieldValueComponent: React.FC<{
|
||||
contextId: string;
|
||||
eventId: string;
|
||||
fieldFormat?: string;
|
||||
|
@ -35,7 +35,7 @@ export const FormattedFieldValue = React.memo<{
|
|||
fieldType: string;
|
||||
truncate?: boolean;
|
||||
value: string | number | undefined | null;
|
||||
}>(({ contextId, eventId, fieldFormat, fieldName, fieldType, truncate, value }) => {
|
||||
}> = ({ contextId, eventId, fieldFormat, fieldName, fieldType, truncate, value }) => {
|
||||
if (fieldType === IP_FIELD_TYPE) {
|
||||
return (
|
||||
<FormattedIp
|
||||
|
@ -126,6 +126,6 @@ export const FormattedFieldValue = React.memo<{
|
|||
</DefaultDraggable>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
FormattedFieldValue.displayName = 'FormattedFieldValue';
|
||||
export const FormattedFieldValue = React.memo(FormattedFieldValueComponent);
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
|
|||
|
||||
import { MlCapabilitiesContext } from '../../components/ml/permissions/ml_capabilities_provider';
|
||||
import { hasMlUserPermissions } from '../../components/ml/permissions/has_ml_user_permissions';
|
||||
import { FlowTarget } from '../../graphql/types';
|
||||
|
||||
import { IPDetails } from './ip_details';
|
||||
import { Network } from './network';
|
||||
|
@ -20,9 +21,9 @@ import { NetworkRouteType } from './navigation/types';
|
|||
type Props = Partial<RouteComponentProps<{}>> & { url: string };
|
||||
|
||||
const networkPagePath = `/:pageName(${SiemPageName.network})`;
|
||||
const ipDetailsPagePath = `${networkPagePath}/ip/:detailName`;
|
||||
const ipDetailsPageBasePath = `${networkPagePath}/ip/:detailName`;
|
||||
|
||||
export const NetworkContainer = React.memo<Props>(() => {
|
||||
const NetworkContainerComponent: React.FC<Props> = () => {
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const capabilitiesFetched = capabilities.capabilitiesFetched;
|
||||
const userHasMlUserPermissions = useMemo(() => hasMlUserPermissions(capabilities), [
|
||||
|
@ -54,14 +55,15 @@ export const NetworkContainer = React.memo<Props>(() => {
|
|||
)}
|
||||
/>
|
||||
<Route
|
||||
path={ipDetailsPagePath}
|
||||
path={`${ipDetailsPageBasePath}/:flowTarget`}
|
||||
render={({
|
||||
match: {
|
||||
params: { detailName },
|
||||
params: { detailName, flowTarget },
|
||||
},
|
||||
}) => (
|
||||
<IPDetails
|
||||
detailName={detailName}
|
||||
flowTarget={flowTarget}
|
||||
to={to}
|
||||
from={from}
|
||||
setQuery={setQuery}
|
||||
|
@ -70,6 +72,19 @@ export const NetworkContainer = React.memo<Props>(() => {
|
|||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={ipDetailsPageBasePath}
|
||||
render={({
|
||||
location: { search = '' },
|
||||
match: {
|
||||
params: { detailName },
|
||||
},
|
||||
}) => (
|
||||
<Redirect
|
||||
to={`/${SiemPageName.network}/ip/${detailName}/${FlowTarget.source}${search}`}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`/${SiemPageName.network}/`}
|
||||
render={({ location: { search = '' } }) => (
|
||||
|
@ -80,6 +95,6 @@ export const NetworkContainer = React.memo<Props>(() => {
|
|||
)}
|
||||
</GlobalTime>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
NetworkContainer.displayName = 'NetworkContainer';
|
||||
export const NetworkContainer = React.memo(NetworkContainerComponent);
|
||||
|
|
|
@ -28,7 +28,7 @@ import { useKibana } from '../../../lib/kibana';
|
|||
import { decodeIpv6 } from '../../../lib/helpers';
|
||||
import { convertToBuildEsQuery } from '../../../lib/keury';
|
||||
import { ConditionalFlexGroup } from '../../../pages/network/navigation/conditional_flex_group';
|
||||
import { networkModel, networkSelectors, State, inputsSelectors } from '../../../store';
|
||||
import { networkModel, State, inputsSelectors } from '../../../store';
|
||||
import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '../../../store/inputs/actions';
|
||||
import { setIpDetailsTablesActivePageToZero as dispatchIpDetailsTablesActivePageToZero } from '../../../store/network/actions';
|
||||
import { SpyRoute } from '../../../utils/route/spy_routes';
|
||||
|
@ -102,7 +102,7 @@ export const IPDetailsComponent = ({
|
|||
subtitle={<LastEventTime indexKey={LastEventIndexKey.ipDetails} ip={ip} />}
|
||||
title={ip}
|
||||
>
|
||||
<FlowTargetSelectConnected />
|
||||
<FlowTargetSelectConnected flowTarget={flowTarget} />
|
||||
</HeaderPage>
|
||||
|
||||
<IpOverviewQuery
|
||||
|
@ -279,11 +279,9 @@ IPDetailsComponent.displayName = 'IPDetailsComponent';
|
|||
const makeMapStateToProps = () => {
|
||||
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector();
|
||||
const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector();
|
||||
const getIpDetailsFlowTargetSelector = networkSelectors.ipDetailsFlowTargetSelector();
|
||||
return (state: State) => ({
|
||||
query: getGlobalQuerySelector(state),
|
||||
filters: getGlobalFiltersQuerySelector(state),
|
||||
flowTarget: getIpDetailsFlowTargetSelector(state),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ export const NetworkTopNFlowQueryTable = ({
|
|||
skip,
|
||||
startDate,
|
||||
type,
|
||||
indexPattern,
|
||||
}: NetworkWithIndexComponentsQueryTableProps) => (
|
||||
<NetworkTopNFlowQuery
|
||||
endDate={endDate}
|
||||
|
@ -50,7 +49,6 @@ export const NetworkTopNFlowQueryTable = ({
|
|||
fakeTotalCount={getOr(50, 'fakeTotalCount', pageInfo)}
|
||||
flowTargeted={flowTarget}
|
||||
id={id}
|
||||
indexPattern={indexPattern}
|
||||
inspect={inspect}
|
||||
isInspect={isInspected}
|
||||
loading={loading}
|
||||
|
|
|
@ -36,7 +36,9 @@ export const getBreadcrumbs = (params: NetworkRouteSpyState, search: string[]):
|
|||
...breadcrumb,
|
||||
{
|
||||
text: decodeIpv6(params.detailName),
|
||||
href: `${getIPDetailsUrl(params.detailName)}${search && search[1] ? search[1] : ''}`,
|
||||
href: `${getIPDetailsUrl(params.detailName, params.flowTarget)}${
|
||||
search && search[1] ? search[1] : ''
|
||||
}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ export const IPsQueryTabBody = ({
|
|||
skip,
|
||||
startDate,
|
||||
setQuery,
|
||||
indexPattern,
|
||||
flowTarget,
|
||||
}: IPsQueryTabBodyProps) => (
|
||||
<NetworkTopNFlowQuery
|
||||
|
@ -50,7 +49,6 @@ export const IPsQueryTabBody = ({
|
|||
fakeTotalCount={getOr(50, 'fakeTotalCount', pageInfo)}
|
||||
flowTargeted={flowTarget}
|
||||
id={id}
|
||||
indexPattern={indexPattern}
|
||||
inspect={inspect}
|
||||
isInspect={isInspected}
|
||||
loading={loading}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import actionCreatorFactory from 'typescript-fsa';
|
||||
|
||||
import { FlowTarget } from '../../graphql/types';
|
||||
import { networkModel } from '../model';
|
||||
|
||||
const actionCreator = actionCreatorFactory('x-pack/siem/local/network');
|
||||
|
@ -24,7 +23,3 @@ export const setIpDetailsTablesActivePageToZero = actionCreator(
|
|||
export const setNetworkTablesActivePageToZero = actionCreator(
|
||||
'SET_NETWORK_TABLES_ACTIVE_PAGE_TO_ZERO'
|
||||
);
|
||||
|
||||
export const updateIpDetailsFlowTarget = actionCreator<{
|
||||
flowTarget: FlowTarget;
|
||||
}>('UPDATE_IP_DETAILS_TARGET');
|
||||
|
|
|
@ -19,14 +19,13 @@ import { DEFAULT_TABLE_ACTIVE_PAGE, DEFAULT_TABLE_LIMIT } from '../constants';
|
|||
import {
|
||||
setIpDetailsTablesActivePageToZero,
|
||||
setNetworkTablesActivePageToZero,
|
||||
updateIpDetailsFlowTarget,
|
||||
updateNetworkTable,
|
||||
} from './actions';
|
||||
import {
|
||||
setNetworkDetailsQueriesActivePageToZero,
|
||||
setNetworkPageQueriesActivePageToZero,
|
||||
} from './helpers';
|
||||
import { IpDetailsTableType, NetworkModel, NetworkTableType, NetworkType } from './model';
|
||||
import { IpDetailsTableType, NetworkModel, NetworkTableType } from './model';
|
||||
|
||||
export type NetworkState = NetworkModel;
|
||||
|
||||
|
@ -189,11 +188,4 @@ export const networkReducer = reducerWithInitialState(initialNetworkState)
|
|||
queries: setNetworkDetailsQueriesActivePageToZero(state),
|
||||
},
|
||||
}))
|
||||
.case(updateIpDetailsFlowTarget, (state, { flowTarget }) => ({
|
||||
...state,
|
||||
[NetworkType.details]: {
|
||||
...state[NetworkType.details],
|
||||
flowTarget,
|
||||
},
|
||||
}))
|
||||
.build();
|
||||
|
|
|
@ -81,9 +81,5 @@ const selectHttpByType = (state: State, networkType: NetworkType) => {
|
|||
|
||||
export const httpSelector = () => createSelector(selectHttpByType, httpQueries => httpQueries);
|
||||
|
||||
// IP Details Selectors
|
||||
export const ipDetailsFlowTargetSelector = () =>
|
||||
createSelector(selectNetworkDetails, network => network.flowTarget);
|
||||
|
||||
export const usersSelector = () =>
|
||||
createSelector(selectNetworkDetails, network => network.queries.users);
|
||||
|
|
|
@ -75,6 +75,7 @@ describe('Spy Routes', () => {
|
|||
detailName: '',
|
||||
tabName: HostsTableType.hosts,
|
||||
search: '',
|
||||
flowTarget: undefined,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
@ -106,6 +107,7 @@ describe('Spy Routes', () => {
|
|||
detailName: undefined,
|
||||
tabName: HostsTableType.hosts,
|
||||
search: '?IdoNotWantToSeeYou="true"',
|
||||
flowTarget: undefined,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
@ -156,6 +158,7 @@ describe('Spy Routes', () => {
|
|||
detailName: undefined,
|
||||
tabName: HostsTableType.hosts,
|
||||
search: '?IdoNotWantToSeeYou="true"',
|
||||
flowTarget: undefined,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -17,7 +17,7 @@ export const SpyRouteComponent = memo<SpyRouteProps & { location: H.Location }>(
|
|||
location: { pathname, search },
|
||||
history,
|
||||
match: {
|
||||
params: { pageName, detailName, tabName },
|
||||
params: { pageName, detailName, tabName, flowTarget },
|
||||
},
|
||||
}) => {
|
||||
const [isInitializing, setIsInitializing] = useState(true);
|
||||
|
@ -43,6 +43,7 @@ export const SpyRouteComponent = memo<SpyRouteProps & { location: H.Location }>(
|
|||
tabName,
|
||||
pathName: pathname,
|
||||
history,
|
||||
flowTarget,
|
||||
},
|
||||
});
|
||||
setIsInitializing(false);
|
||||
|
@ -56,11 +57,12 @@ export const SpyRouteComponent = memo<SpyRouteProps & { location: H.Location }>(
|
|||
search,
|
||||
pathName: pathname,
|
||||
history,
|
||||
flowTarget,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [pathname, search, pageName, detailName, tabName]);
|
||||
}, [pathname, search, pageName, detailName, tabName, flowTarget]);
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
|
|
@ -10,6 +10,7 @@ import { RouteComponentProps } from 'react-router-dom';
|
|||
|
||||
import { HostsTableType } from '../../store/hosts/model';
|
||||
import { NetworkRouteType } from '../../pages/network/navigation/types';
|
||||
import { FlowTarget } from '../../graphql/types';
|
||||
|
||||
export type SiemRouteType = HostsTableType | NetworkRouteType;
|
||||
export interface RouteSpyState {
|
||||
|
@ -19,6 +20,7 @@ export interface RouteSpyState {
|
|||
search: string;
|
||||
pathName: string;
|
||||
history?: H.History;
|
||||
flowTarget?: FlowTarget;
|
||||
}
|
||||
|
||||
export interface HostRouteSpyState extends RouteSpyState {
|
||||
|
@ -52,4 +54,5 @@ export type SpyRouteProps = RouteComponentProps<{
|
|||
detailName: string | undefined;
|
||||
tabName: HostsTableType | undefined;
|
||||
search: string;
|
||||
flowTarget: FlowTarget | undefined;
|
||||
}>;
|
||||
|
|
Loading…
Reference in a new issue