diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/alert_summary_view.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/alert_summary_view.test.tsx.snap index 2904d8184261e..d367c68586be1 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/alert_summary_view.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/alert_summary_view.test.tsx.snap @@ -210,7 +210,7 @@ exports[`AlertSummaryView Behavior event code renders additional summary rows 1` class="eventFieldsTable__fieldValue" > Nov 25, 2020 @ 15:42:39.417 @@ -872,7 +872,7 @@ exports[`AlertSummaryView Memory event code renders additional summary rows 1`] class="eventFieldsTable__fieldValue" > Nov 25, 2020 @ 15:42:39.417 diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts b/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts index 76ca5dfe53f4e..23acd90183af4 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts @@ -25,7 +25,7 @@ import { } from '../../../../timelines/components/timeline/body/renderers/constants'; import { BYTES_FORMAT } from '../../../../timelines/components/timeline/body/renderers/bytes'; import { EVENT_DURATION_FIELD_NAME } from '../../../../timelines/components/duration'; -import { PORT_NAMES } from '../../../../network/components/port'; +import { PORT_NAMES } from '../../../../network/components/port/helpers'; import { INDICATOR_REFERENCE } from '../../../../../common/cti/constants'; import { BrowserField } from '../../../containers/source'; import { DataProvider, IS_OPERATOR } from '../../../../../common/types'; diff --git a/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx b/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx index f6df6e132ebee..e525003660a85 100644 --- a/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx @@ -92,23 +92,27 @@ PreferenceFormattedP1DTDate.displayName = 'PreferenceFormattedP1DTDate'; * - a long representation of the date that includes the day of the week (e.g. Thursday, March 21, 2019 6:47pm) * - the raw date value (e.g. 2019-03-22T00:47:46Z) */ -export const FormattedDate = React.memo<{ + +interface FormattedDateProps { + className?: string; fieldName: string; value?: string | number | null; - className?: string; -}>(({ value, fieldName, className = '' }): JSX.Element => { - if (value == null) { - return getOrEmptyTagFromValue(value); +} +export const FormattedDate = React.memo( + ({ value, fieldName, className = '' }): JSX.Element => { + if (value == null) { + return getOrEmptyTagFromValue(value); + } + const maybeDate = getMaybeDate(value); + return maybeDate.isValid() ? ( + + + + ) : ( + getOrEmptyTagFromValue(value) + ); } - const maybeDate = getMaybeDate(value); - return maybeDate.isValid() ? ( - - - - ) : ( - getOrEmptyTagFromValue(value) - ); -}); +); FormattedDate.displayName = 'FormattedDate'; diff --git a/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap index d3d20c7183570..fe589b1e253a9 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap @@ -6,23 +6,25 @@ exports[`HeaderSection it renders 1`] = ` > -

Test title -

+
= ({ hideSubtitle = false, }) => (
- + - + -

+

{title} {tooltip && ( <> @@ -81,7 +81,7 @@ const HeaderSectionComponent: React.FC = ({ )} -

+
{!hideSubtitle && ( diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx index e1546c5220e22..738103f02dcdf 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx @@ -30,33 +30,49 @@ const SHOW_TOP = (fieldName: string) => }); interface Props { + className?: string; /** When `Component` is used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality. * When `Component` is used with `EuiContextMenu`, we pass EuiContextMenuItem to render the right style. */ Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon | typeof EuiContextMenuItem; enablePopOver?: boolean; field: string; + flush?: 'left' | 'right' | 'both'; globalFilters?: Filter[]; + iconSide?: 'left' | 'right'; + iconType?: string; + isExpandable?: boolean; onClick: () => void; onFilterAdded?: () => void; ownFocus: boolean; + paddingSize?: 's' | 'm' | 'l' | 'none'; showTooltip?: boolean; showTopN: boolean; + showLegend?: boolean; timelineId?: string | null; + title?: string; value?: string[] | string | null; } export const ShowTopNButton: React.FC = React.memo( ({ + className, Component, enablePopOver, field, + flush, + iconSide, + iconType, + isExpandable, onClick, onFilterAdded, ownFocus, + paddingSize, + showLegend, showTooltip = true, showTopN, timelineId, + title, value, globalFilters, }) => { @@ -70,31 +86,36 @@ export const ShowTopNButton: React.FC = React.memo( ? SourcererScopeName.detections : SourcererScopeName.default; const { browserFields, indexPattern } = useSourcererScope(activeScope); - + const icon = iconType ?? 'visBarVertical'; + const side = iconSide ?? 'left'; + const buttonTitle = title ?? SHOW_TOP(field); const basicButton = useMemo( () => Component ? ( - {SHOW_TOP(field)} + {buttonTitle} ) : ( ), - [Component, field, onClick] + [Component, buttonTitle, className, flush, icon, onClick, side] ); const button = useMemo( @@ -107,7 +128,7 @@ export const ShowTopNButton: React.FC = React.memo( field, value, })} - content={SHOW_TOP(field)} + content={buttonTitle} shortcut={SHOW_TOP_N_KEYBOARD_SHORTCUT} showShortcut={ownFocus} /> @@ -118,7 +139,7 @@ export const ShowTopNButton: React.FC = React.memo( ) : ( basicButton ), - [basicButton, field, ownFocus, showTooltip, showTopN, value] + [basicButton, buttonTitle, field, ownFocus, showTooltip, showTopN, value] ); const topNPannel = useMemo( @@ -128,15 +149,37 @@ export const ShowTopNButton: React.FC = React.memo( field={field} indexPattern={indexPattern} onFilterAdded={onFilterAdded} + paddingSize={paddingSize} + showLegend={showLegend} timelineId={timelineId ?? undefined} toggleTopN={onClick} value={value} globalFilters={globalFilters} /> ), - [browserFields, field, indexPattern, onClick, onFilterAdded, timelineId, value, globalFilters] + [ + browserFields, + field, + indexPattern, + onFilterAdded, + paddingSize, + showLegend, + timelineId, + onClick, + value, + globalFilters, + ] ); + if (isExpandable) { + return ( + <> + {basicButton} + {showTopN && topNPannel} + + ); + } + return showTopN ? ( enablePopOver ? ( | PropsForAnchor> = + ({ children, ...props }) => {children}; + +export const LinkAnchor: React.FC = ({ children, ...props }) => ( + {children} +); + +export const Comma = styled('span')` + margin-right: 5px; + margin-left: 5px; + &::after { + content: ' ,'; + } +`; + +Comma.displayName = 'Comma'; + +const GenericLinkButtonComponent: React.FC<{ + children?: React.ReactNode; + /** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */ + Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon; + dataTestSubj?: string; + href: string; + onClick?: (e: SyntheticEvent) => void; + title?: string; + iconType?: string; +}> = ({ children, Component, dataTestSubj, href, onClick, title, iconType = 'expand' }) => { + return Component ? ( + + {title ?? children} + + ) : ( + + {title ?? children} + + ); +}; + +export const GenericLinkButton = React.memo(GenericLinkButtonComponent); + +export const PortContainer = styled.div` + & svg { + position: relative; + top: -1px; + } +`; diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.tsx index 7db6b0204b649..c74791b8b3aa7 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.tsx @@ -6,19 +6,15 @@ */ import { - EuiButton, - EuiButtonProps, - EuiLink, - EuiLinkProps, - EuiToolTip, + EuiButtonEmpty, + EuiButtonIcon, EuiFlexGroup, EuiFlexItem, - PropsForAnchor, - PropsForButton, + EuiLink, + EuiToolTip, } from '@elastic/eui'; import React, { useMemo, useCallback, SyntheticEvent } from 'react'; import { isNil } from 'lodash/fp'; -import styled from 'styled-components'; import { IP_REPUTATION_LINKS_SETTING, APP_ID } from '../../../../common/constants'; import { @@ -43,22 +39,11 @@ import { isUrlInvalid } from '../../utils/validators'; import * as i18n from './translations'; import { SecurityPageName } from '../../../app/types'; import { getUebaDetailsUrl } from '../link_to/redirect_to_ueba'; +import { LinkButton, LinkAnchor, GenericLinkButton, PortContainer, Comma } from './helpers'; -export const DEFAULT_NUMBER_OF_LINK = 5; - -export const LinkButton: React.FC | PropsForAnchor> = - ({ children, ...props }) => {children}; +export { LinkButton, LinkAnchor } from './helpers'; -export const LinkAnchor: React.FC = ({ children, ...props }) => ( - {children} -); - -export const PortContainer = styled.div` - & svg { - position: relative; - top: -1px; - } -`; +export const DEFAULT_NUMBER_OF_LINK = 5; // Internal Links const UebaDetailsLinkComponent: React.FC<{ @@ -102,10 +87,13 @@ export const UebaDetailsLink = React.memo(UebaDetailsLinkComponent); const HostDetailsLinkComponent: React.FC<{ children?: React.ReactNode; + /** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */ + Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon; hostName: string; isButton?: boolean; onClick?: (e: SyntheticEvent) => void; -}> = ({ children, hostName, isButton, onClick }) => { + title?: string; +}> = ({ children, Component, hostName, isButton, onClick, title }) => { const { formatUrl, search } = useFormatUrl(SecurityPageName.hosts); const { navigateToApp } = useKibana().services.application; const goToHostDetails = useCallback( @@ -118,19 +106,25 @@ const HostDetailsLinkComponent: React.FC<{ }, [hostName, navigateToApp, search] ); - + const href = useMemo( + () => formatUrl(getHostDetailsUrl(encodeURIComponent(hostName))), + [formatUrl, hostName] + ); return isButton ? ( - - {children ? children : hostName} - + {children} + ) : ( {children ? children : hostName} @@ -176,11 +170,14 @@ ExternalLink.displayName = 'ExternalLink'; const NetworkDetailsLinkComponent: React.FC<{ children?: React.ReactNode; + /** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */ + Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon; ip: string; flowTarget?: FlowTarget | FlowTargetSourceDest; isButton?: boolean; onClick?: (e: SyntheticEvent) => void | undefined; -}> = ({ children, ip, flowTarget = FlowTarget.source, isButton, onClick }) => { + title?: string; +}> = ({ Component, children, ip, flowTarget = FlowTarget.source, isButton, onClick, title }) => { const { formatUrl, search } = useFormatUrl(SecurityPageName.network); const { navigateToApp } = useKibana().services.application; const goToNetworkDetails = useCallback( @@ -193,19 +190,25 @@ const NetworkDetailsLinkComponent: React.FC<{ }, [flowTarget, ip, navigateToApp, search] ); + const href = useMemo( + () => formatUrl(getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(ip)))), + [formatUrl, ip] + ); return isButton ? ( - - {children ? children : ip} - + {children} + ) : ( {children ? children : ip} @@ -272,63 +275,84 @@ CreateCaseLink.displayName = 'CreateCaseLink'; // External Links export const GoogleLink = React.memo<{ children?: React.ReactNode; link: string }>( - ({ children, link }) => ( - - {children ? children : link} - - ) + ({ children, link }) => { + const url = useMemo( + () => `https://www.google.com/search?q=${encodeURIComponent(link)}`, + [link] + ); + return {children ? children : link}; + } ); GoogleLink.displayName = 'GoogleLink'; export const PortOrServiceNameLink = React.memo<{ children?: React.ReactNode; + /** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */ + Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon; portOrServiceName: number | string; -}>(({ children, portOrServiceName }) => ( - - void | undefined; + title?: string; +}>(({ Component, title, children, portOrServiceName }) => { + const href = useMemo( + () => + `https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=${encodeURIComponent( String(portOrServiceName) - )}`} - target="_blank" + )}`, + [portOrServiceName] + ); + return Component ? ( + - {children ? children : portOrServiceName} - - -)); + {title ?? children ?? portOrServiceName} + + ) : ( + + + {children ? children : portOrServiceName} + + + ); +}); PortOrServiceNameLink.displayName = 'PortOrServiceNameLink'; export const Ja3FingerprintLink = React.memo<{ children?: React.ReactNode; ja3Fingerprint: string; -}>(({ children, ja3Fingerprint }) => ( - - {children ? children : ja3Fingerprint} - -)); +}>(({ children, ja3Fingerprint }) => { + const href = useMemo( + () => `https://sslbl.abuse.ch/ja3-fingerprints/${encodeURIComponent(ja3Fingerprint)}`, + [ja3Fingerprint] + ); + return ( + + {children ? children : ja3Fingerprint} + + ); +}); Ja3FingerprintLink.displayName = 'Ja3FingerprintLink'; export const CertificateFingerprintLink = React.memo<{ children?: React.ReactNode; certificateFingerprint: string; -}>(({ children, certificateFingerprint }) => ( - - {children ? children : certificateFingerprint} - -)); +}>(({ children, certificateFingerprint }) => { + const href = useMemo( + () => + `https://sslbl.abuse.ch/ssl-certificates/sha1/${encodeURIComponent(certificateFingerprint)}`, + [certificateFingerprint] + ); + return ( + + {children ? children : certificateFingerprint} + + ); +}); CertificateFingerprintLink.displayName = 'CertificateFingerprintLink'; @@ -354,16 +378,6 @@ const isReputationLink = ( (rowItem as ReputationLinkSetting).url_template !== undefined && (rowItem as ReputationLinkSetting).name !== undefined; -export const Comma = styled('span')` - margin-right: 5px; - margin-left: 5px; - &::after { - content: ' ,'; - } -`; - -Comma.displayName = 'Comma'; - const defaultNameMapping: Record = { [DefaultReputationLink['virustotal.com']]: i18n.VIEW_VIRUS_TOTAL, [DefaultReputationLink['talosIntelligence.com']]: i18n.VIEW_TALOS_INTELLIGENCE, @@ -463,11 +477,13 @@ ReputationLinkComponent.displayName = 'ReputationLinkComponent'; export const ReputationLink = React.memo(ReputationLinkComponent); export const WhoIsLink = React.memo<{ children?: React.ReactNode; domain: string }>( - ({ children, domain }) => ( - - {children ? children : domain} - - ) + ({ children, domain }) => { + const url = useMemo( + () => `https://www.iana.org/whois?q=${encodeURIComponent(domain)}`, + [domain] + ); + return {children ? children : domain}; + } ); WhoIsLink.displayName = 'WhoIsLink'; diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index f25e8311ff8fe..7eac477741a5c 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -79,6 +79,7 @@ export const MatrixHistogramComponent: React.FC = legendPosition, mapping, onError, + paddingSize = 'm', panelHeight = DEFAULT_PANEL_HEIGHT, setAbsoluteRangeDatePickerTarget = 'global', setQuery, @@ -200,7 +201,11 @@ export const MatrixHistogramComponent: React.FC = return ( <> - + {loading && !isInitialLoading && (