[Security Solution][Endpoint] Displays Trusted apps card in policy fleet integration page - UI (#111708)
* Displays Trusted apps card in policy fleet integration page * Fixes translation * Use FF to display the card or not * Revert FF to false by defaul * Fix back external button props in router stats * redesign TA card for fleet policy page * Address pr comments. Added experimental feature singleton service. Added FTR test for TA card in fleet policy * Change wrong fleet name and SIEM to Security Solutions * Fix test-subject prop because test is failing * Use different designs in fleet and integration. Pending to confirm by design team * UI changes on summary cards * Adds same card design in fleet policy and integration policy pages * Fix ts error making prop as optional * Use eui props for sizing. Use reverseRow to switch label/numbers depending on isSmall prop Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e4f8e64e71
commit
d75df38786
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ExperimentalFeatures } from '../../common/experimental_features';
|
||||
|
||||
export class ExperimentalFeaturesService {
|
||||
private static experimentalFeatures?: ExperimentalFeatures;
|
||||
|
||||
public static init({ experimentalFeatures }: { experimentalFeatures: ExperimentalFeatures }) {
|
||||
this.experimentalFeatures = experimentalFeatures;
|
||||
}
|
||||
|
||||
public static get(): ExperimentalFeatures {
|
||||
if (!this.experimentalFeatures) {
|
||||
this.throwUninitializedError();
|
||||
}
|
||||
|
||||
return this.experimentalFeatures;
|
||||
}
|
||||
|
||||
private static throwUninitializedError(): never {
|
||||
throw new Error(
|
||||
'Experimental features services not initialized - are you trying to import this module from outside of the Security Solution app?'
|
||||
);
|
||||
}
|
||||
}
|
|
@ -5,9 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, memo, useCallback } from 'react';
|
||||
import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import React, { FC, memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import styled from 'styled-components';
|
||||
import { GetExceptionSummaryResponse } from '../../../../../../../../common/endpoint/types';
|
||||
|
||||
const SUMMARY_KEYS: Readonly<Array<keyof GetExceptionSummaryResponse>> = [
|
||||
|
@ -36,46 +37,76 @@ const SUMMARY_LABELS: Readonly<{ [key in keyof GetExceptionSummaryResponse]: str
|
|||
),
|
||||
};
|
||||
|
||||
export const StyledEuiFlexGridGroup = styled(EuiFlexGroup)`
|
||||
display: grid;
|
||||
min-width: 240px;
|
||||
grid-template-columns: 50% 50%;
|
||||
`;
|
||||
|
||||
const StyledEuiFlexGroup = styled(EuiFlexGroup)<{
|
||||
isSmall: boolean;
|
||||
}>`
|
||||
font-size: ${({ isSmall, theme }) => (isSmall ? theme.eui.euiFontSizeXS : 'innherit')};
|
||||
font-weight: ${({ isSmall }) => (isSmall ? '1px' : 'innherit')};
|
||||
`;
|
||||
|
||||
const CSS_BOLD: Readonly<React.CSSProperties> = { fontWeight: 'bold' };
|
||||
|
||||
interface ExceptionItemsSummaryProps {
|
||||
stats: GetExceptionSummaryResponse | undefined;
|
||||
isSmall?: boolean;
|
||||
}
|
||||
|
||||
export const ExceptionItemsSummary = memo<ExceptionItemsSummaryProps>(({ stats }) => {
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceAround">
|
||||
{SUMMARY_KEYS.map((stat) => {
|
||||
return (
|
||||
<EuiFlexItem key={stat}>
|
||||
<SummaryStat
|
||||
value={stats?.[stat] ?? 0}
|
||||
color={stat === 'total' ? 'primary' : 'default'}
|
||||
key={stat}
|
||||
>
|
||||
{SUMMARY_LABELS[stat]}
|
||||
</SummaryStat>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
export const ExceptionItemsSummary = memo<ExceptionItemsSummaryProps>(
|
||||
({ stats, isSmall = false }) => {
|
||||
const getItem = useCallback(
|
||||
(stat: keyof GetExceptionSummaryResponse) => (
|
||||
<EuiFlexItem key={stat}>
|
||||
<SummaryStat
|
||||
value={stats?.[stat] ?? 0}
|
||||
color={stat === 'total' ? 'primary' : 'default'}
|
||||
key={stat}
|
||||
isSmall={isSmall}
|
||||
>
|
||||
{SUMMARY_LABELS[stat]}
|
||||
</SummaryStat>
|
||||
</EuiFlexItem>
|
||||
),
|
||||
[stats, isSmall]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent={isSmall ? 'flexStart' : 'spaceAround'}
|
||||
gutterSize={isSmall ? 's' : 'l'}
|
||||
>
|
||||
{SUMMARY_KEYS.map((stat) => getItem(stat))}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ExceptionItemsSummary.displayName = 'ExceptionItemsSummary';
|
||||
|
||||
const SummaryStat: FC<{ value: number; color?: EuiBadgeProps['color'] }> = memo(
|
||||
({ children, value, color, ...commonProps }) => {
|
||||
const SummaryStat: FC<{ value: number; color?: EuiBadgeProps['color']; isSmall?: boolean }> = memo(
|
||||
({ children, value, color, isSmall = false, ...commonProps }) => {
|
||||
return (
|
||||
<EuiText className="eui-displayInlineBlock" size="s">
|
||||
<EuiFlexGroup justifyContent="center" direction="row" alignItems="center">
|
||||
<EuiText className="eui-displayInlineBlock" size={isSmall ? 'xs' : 's'}>
|
||||
<StyledEuiFlexGroup
|
||||
justifyContent={isSmall ? 'flexStart' : 'center'}
|
||||
direction={isSmall ? 'rowReverse' : 'row'}
|
||||
alignItems="center"
|
||||
gutterSize={isSmall ? 'xs' : 'l'}
|
||||
isSmall={isSmall}
|
||||
>
|
||||
<EuiFlexItem grow={false} style={color === 'primary' ? CSS_BOLD : undefined}>
|
||||
{children}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color={color}>{value}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</StyledEuiFlexGroup>
|
||||
</EuiText>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -46,15 +46,17 @@ export const FleetEventFiltersCard = memo<PackageCustomExtensionComponentProps>(
|
|||
setStats(summary);
|
||||
}
|
||||
} catch (error) {
|
||||
toasts.addDanger(
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.eventFiltersSummaryError',
|
||||
{
|
||||
defaultMessage: 'There was an error trying to fetch event filters stats: "{error}"',
|
||||
values: { error },
|
||||
}
|
||||
)
|
||||
);
|
||||
if (isMounted.current) {
|
||||
toasts.addDanger(
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.eventFiltersSummaryError',
|
||||
{
|
||||
defaultMessage: 'There was an error trying to fetch event filters stats: "{error}"',
|
||||
values: { error },
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
fetchStats();
|
||||
|
@ -78,12 +80,15 @@ export const FleetEventFiltersCard = memo<PackageCustomExtensionComponentProps>(
|
|||
path: fleetPackageCustomUrlPath,
|
||||
},
|
||||
],
|
||||
backButtonUrl: getAppUrl({ appId: INTEGRATIONS_PLUGIN_ID, path: fleetPackageCustomUrlPath }),
|
||||
backButtonUrl: getAppUrl({
|
||||
appId: INTEGRATIONS_PLUGIN_ID,
|
||||
path: fleetPackageCustomUrlPath,
|
||||
}),
|
||||
};
|
||||
}, [getAppUrl, pkgkey]);
|
||||
|
||||
return (
|
||||
<EuiPanel paddingSize="l">
|
||||
<EuiPanel hasShadow={false} paddingSize="l" hasBorder data-test-subj="fleedEventFiltersCard">
|
||||
<StyledEuiFlexGridGroup alignItems="baseline" justifyContent="center">
|
||||
<StyledEuiFlexGridItem gridarea="title" alignitems="flex-start">
|
||||
<EuiText>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { FleetTrustedAppsCard } from './fleet_trusted_apps_card';
|
||||
import { FleetTrustedAppsCardWrapper } from './fleet_trusted_apps_card_wrapper';
|
||||
import * as reactTestingLibrary from '@testing-library/react';
|
||||
import { TrustedAppsHttpService } from '../../../../../trusted_apps/service';
|
||||
import { useToasts } from '../../../../../../../common/lib/kibana';
|
||||
|
@ -67,7 +67,9 @@ describe('Fleet trusted apps card', () => {
|
|||
</I18nProvider>
|
||||
);
|
||||
// @ts-ignore
|
||||
const component = reactTestingLibrary.render(<FleetTrustedAppsCard />, { wrapper: Wrapper });
|
||||
const component = reactTestingLibrary.render(<FleetTrustedAppsCardWrapper />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
try {
|
||||
// @ts-ignore
|
||||
await reactTestingLibrary.act(() => promise);
|
||||
|
|
|
@ -9,116 +9,87 @@ import React, { memo, useMemo, useState, useEffect, useRef } from 'react';
|
|||
import { EuiPanel, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
PackageCustomExtensionComponentProps,
|
||||
pagePathGetters,
|
||||
} from '../../../../../../../../../fleet/public';
|
||||
import { getTrustedAppsListPath } from '../../../../../../common/routing';
|
||||
import {
|
||||
ListPageRouteState,
|
||||
GetExceptionSummaryResponse,
|
||||
} from '../../../../../../../../common/endpoint/types';
|
||||
import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../../../fleet/common';
|
||||
import { GetExceptionSummaryResponse } from '../../../../../../../../common/endpoint/types';
|
||||
|
||||
import { useAppUrl } from '../../../../../../../common/lib/kibana/hooks';
|
||||
import { useKibana, useToasts } from '../../../../../../../common/lib/kibana';
|
||||
import { LinkWithIcon } from './link_with_icon';
|
||||
import { ExceptionItemsSummary } from './exception_items_summary';
|
||||
import { TrustedAppsHttpService } from '../../../../../trusted_apps/service';
|
||||
import { StyledEuiFlexGridGroup, StyledEuiFlexGridItem } from './styled_components';
|
||||
|
||||
export const FleetTrustedAppsCard = memo<PackageCustomExtensionComponentProps>(({ pkgkey }) => {
|
||||
const { getAppUrl } = useAppUrl();
|
||||
const {
|
||||
services: { http },
|
||||
} = useKibana();
|
||||
const toasts = useToasts();
|
||||
const [stats, setStats] = useState<GetExceptionSummaryResponse | undefined>();
|
||||
const trustedAppsApi = useMemo(() => new TrustedAppsHttpService(http), [http]);
|
||||
const isMounted = useRef<boolean>();
|
||||
interface FleetTrustedAppsCardProps {
|
||||
customLink: React.ReactNode;
|
||||
policyId?: string;
|
||||
cardSize?: 'm' | 'l';
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
const response = await trustedAppsApi.getTrustedAppsSummary();
|
||||
if (isMounted) {
|
||||
setStats(response);
|
||||
export const FleetTrustedAppsCard = memo<FleetTrustedAppsCardProps>(
|
||||
({ customLink, policyId, cardSize = 'l' }) => {
|
||||
const {
|
||||
services: { http },
|
||||
} = useKibana();
|
||||
const toasts = useToasts();
|
||||
const [stats, setStats] = useState<GetExceptionSummaryResponse | undefined>();
|
||||
const trustedAppsApi = useMemo(() => new TrustedAppsHttpService(http), [http]);
|
||||
const isMounted = useRef<boolean>();
|
||||
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
const response = await trustedAppsApi.getTrustedAppsSummary({
|
||||
kuery: policyId
|
||||
? `exception-list-agnostic.attributes.tags:"policy:${policyId}" OR exception-list-agnostic.attributes.tags:"policy:all"`
|
||||
: undefined,
|
||||
});
|
||||
if (isMounted) {
|
||||
setStats(response);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isMounted.current) {
|
||||
toasts.addDanger(
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppsSummaryError',
|
||||
{
|
||||
defaultMessage:
|
||||
'There was an error trying to fetch trusted apps stats: "{error}"',
|
||||
values: { error },
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
toasts.addDanger(
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppsSummaryError',
|
||||
{
|
||||
defaultMessage: 'There was an error trying to fetch trusted apps stats: "{error}"',
|
||||
values: { error },
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
fetchStats();
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, [toasts, trustedAppsApi]);
|
||||
const trustedAppsListUrlPath = getTrustedAppsListPath();
|
||||
};
|
||||
fetchStats();
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, [toasts, trustedAppsApi, policyId]);
|
||||
|
||||
const trustedAppRouteState = useMemo<ListPageRouteState>(() => {
|
||||
const fleetPackageCustomUrlPath = `#${
|
||||
pagePathGetters.integration_details_custom({ pkgkey })[1]
|
||||
}`;
|
||||
const getTitleMessage = () => (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppsLabel"
|
||||
defaultMessage="Trusted Applications"
|
||||
/>
|
||||
);
|
||||
|
||||
return {
|
||||
backButtonLabel: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel',
|
||||
{ defaultMessage: 'Back to Endpoint Integration' }
|
||||
),
|
||||
onBackButtonNavigateTo: [
|
||||
INTEGRATIONS_PLUGIN_ID,
|
||||
{
|
||||
path: fleetPackageCustomUrlPath,
|
||||
},
|
||||
],
|
||||
backButtonUrl: getAppUrl({ appId: INTEGRATIONS_PLUGIN_ID, path: fleetPackageCustomUrlPath }),
|
||||
};
|
||||
}, [getAppUrl, pkgkey]);
|
||||
return (
|
||||
<EuiPanel paddingSize="l">
|
||||
<StyledEuiFlexGridGroup alignItems="baseline" justifyContent="center">
|
||||
<StyledEuiFlexGridItem gridarea="title" alignitems="flex-start">
|
||||
<EuiText>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppsLabel"
|
||||
defaultMessage="Trusted Applications"
|
||||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
</StyledEuiFlexGridItem>
|
||||
<StyledEuiFlexGridItem gridarea="summary">
|
||||
<ExceptionItemsSummary stats={stats} />
|
||||
</StyledEuiFlexGridItem>
|
||||
<StyledEuiFlexGridItem gridarea="link" alignitems="flex-end">
|
||||
<>
|
||||
<LinkWithIcon
|
||||
href={getAppUrl({
|
||||
path: trustedAppsListUrlPath,
|
||||
})}
|
||||
appPath={trustedAppsListUrlPath}
|
||||
appState={trustedAppRouteState}
|
||||
data-test-subj="linkToTrustedApps"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.fleetCustomExtension.manageTrustedAppLinkLabel"
|
||||
defaultMessage="Manage trusted applications"
|
||||
/>
|
||||
</LinkWithIcon>
|
||||
</>
|
||||
</StyledEuiFlexGridItem>
|
||||
</StyledEuiFlexGridGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<EuiPanel hasShadow={false} paddingSize="l" hasBorder data-test-subj="fleetTrustedAppsCard">
|
||||
<StyledEuiFlexGridGroup alignItems="baseline" justifyContent="center" cardSize={cardSize}>
|
||||
<StyledEuiFlexGridItem gridarea="title" alignitems="flex-start">
|
||||
<EuiText>
|
||||
{cardSize === 'l' ? <h4>{getTitleMessage()}</h4> : <h5>{getTitleMessage()}</h5>}
|
||||
</EuiText>
|
||||
</StyledEuiFlexGridItem>
|
||||
<StyledEuiFlexGridItem gridarea="summary">
|
||||
<ExceptionItemsSummary stats={stats} isSmall={cardSize === 'm'} />
|
||||
</StyledEuiFlexGridItem>
|
||||
<StyledEuiFlexGridItem gridarea="link" alignitems="flex-end">
|
||||
{customLink}
|
||||
</StyledEuiFlexGridItem>
|
||||
</StyledEuiFlexGridGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
FleetTrustedAppsCard.displayName = 'FleetTrustedAppsCard';
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
PackageCustomExtensionComponentProps,
|
||||
pagePathGetters,
|
||||
} from '../../../../../../../../../fleet/public';
|
||||
import { getTrustedAppsListPath } from '../../../../../../common/routing';
|
||||
import { ListPageRouteState } from '../../../../../../../../common/endpoint/types';
|
||||
import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../../../fleet/common';
|
||||
|
||||
import { useAppUrl } from '../../../../../../../common/lib/kibana/hooks';
|
||||
import { LinkWithIcon } from './link_with_icon';
|
||||
import { FleetTrustedAppsCard } from './fleet_trusted_apps_card';
|
||||
|
||||
export const FleetTrustedAppsCardWrapper = memo<PackageCustomExtensionComponentProps>(
|
||||
({ pkgkey }) => {
|
||||
const { getAppUrl } = useAppUrl();
|
||||
const trustedAppsListUrlPath = getTrustedAppsListPath();
|
||||
|
||||
const trustedAppRouteState = useMemo<ListPageRouteState>(() => {
|
||||
const fleetPackageCustomUrlPath = `#${
|
||||
pagePathGetters.integration_details_custom({ pkgkey })[1]
|
||||
}`;
|
||||
|
||||
return {
|
||||
backButtonLabel: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel',
|
||||
{ defaultMessage: 'Back to Endpoint Integration' }
|
||||
),
|
||||
onBackButtonNavigateTo: [
|
||||
INTEGRATIONS_PLUGIN_ID,
|
||||
{
|
||||
path: fleetPackageCustomUrlPath,
|
||||
},
|
||||
],
|
||||
backButtonUrl: getAppUrl({
|
||||
appId: INTEGRATIONS_PLUGIN_ID,
|
||||
path: fleetPackageCustomUrlPath,
|
||||
}),
|
||||
};
|
||||
}, [getAppUrl, pkgkey]);
|
||||
|
||||
const customLink = useMemo(
|
||||
() => (
|
||||
<LinkWithIcon
|
||||
href={getAppUrl({
|
||||
path: trustedAppsListUrlPath,
|
||||
})}
|
||||
appPath={trustedAppsListUrlPath}
|
||||
appState={trustedAppRouteState}
|
||||
data-test-subj="linkToTrustedApps"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.fleetCustomExtension.manageTrustedAppLinkLabel"
|
||||
defaultMessage="Manage trusted applications"
|
||||
/>
|
||||
</LinkWithIcon>
|
||||
),
|
||||
[getAppUrl, trustedAppRouteState, trustedAppsListUrlPath]
|
||||
);
|
||||
return <FleetTrustedAppsCard customLink={customLink} />;
|
||||
}
|
||||
);
|
||||
|
||||
FleetTrustedAppsCardWrapper.displayName = 'FleetTrustedAppsCardWrapper';
|
|
@ -13,16 +13,23 @@ import {
|
|||
LinkToAppProps,
|
||||
} from '../../../../../../../common/components/endpoint/link_to_app';
|
||||
|
||||
const LinkLabel = styled.span`
|
||||
const LinkLabel = styled.span<{
|
||||
size?: 'm' | 'l';
|
||||
}>`
|
||||
display: inline-block;
|
||||
padding-right: ${(props) => props.theme.eui.paddingSizes.s};
|
||||
font-size: ${({ size, theme }) => (size === 'm' ? theme.eui.euiFontSizeXS : 'innherit')};
|
||||
`;
|
||||
|
||||
export const LinkWithIcon: FC<LinkToAppProps> = memo(({ children, ...props }) => {
|
||||
type ComponentProps = LinkToAppProps & {
|
||||
size?: 'm' | 'l';
|
||||
};
|
||||
|
||||
export const LinkWithIcon: FC<ComponentProps> = memo(({ children, size = 'l', ...props }) => {
|
||||
return (
|
||||
<LinkToApp {...props}>
|
||||
<LinkLabel>{children}</LinkLabel>
|
||||
<EuiIcon type="popout" />
|
||||
<LinkLabel size={size}>{children}</LinkLabel>
|
||||
<EuiIcon type={size === 'm' ? 'arrowRight' : 'popout'} />
|
||||
</LinkToApp>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -7,9 +7,12 @@
|
|||
import styled from 'styled-components';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
export const StyledEuiFlexGridGroup = styled(EuiFlexGroup)`
|
||||
export const StyledEuiFlexGridGroup = styled(EuiFlexGroup)<{
|
||||
cardSize?: 'm' | 'l';
|
||||
}>`
|
||||
display: grid;
|
||||
grid-template-columns: 25% 45% 30%;
|
||||
grid-template-columns: ${({ cardSize = 'l' }) =>
|
||||
cardSize === 'l' ? '25% 45% 30%' : '30% 35% 35%'};
|
||||
grid-template-areas: 'title summary link';
|
||||
`;
|
||||
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
import { EuiSpacer } from '@elastic/eui';
|
||||
import React, { memo } from 'react';
|
||||
import { PackageCustomExtensionComponentProps } from '../../../../../../../../fleet/public';
|
||||
import { FleetTrustedAppsCard } from './components/fleet_trusted_apps_card';
|
||||
import { FleetTrustedAppsCardWrapper } from './components/fleet_trusted_apps_card_wrapper';
|
||||
import { FleetEventFiltersCard } from './components/fleet_event_filters_card';
|
||||
|
||||
export const EndpointPackageCustomExtension = memo<PackageCustomExtensionComponentProps>(
|
||||
(props) => {
|
||||
return (
|
||||
<div data-test-subj="fleetEndpointPackageCustomContent">
|
||||
<FleetTrustedAppsCard {...props} />
|
||||
<FleetTrustedAppsCardWrapper {...props} />
|
||||
<EuiSpacer />
|
||||
<FleetEventFiltersCard {...props} />
|
||||
</div>
|
||||
|
|
|
@ -5,19 +5,27 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useEffect, useState } from 'react';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import React, { memo, useEffect, useState, useMemo } from 'react';
|
||||
import { EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import {
|
||||
PackagePolicyEditExtensionComponentProps,
|
||||
NewPackagePolicy,
|
||||
pagePathGetters,
|
||||
} from '../../../../../../../fleet/public';
|
||||
import { getPolicyDetailPath } from '../../../../common/routing';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
|
||||
import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../fleet/common';
|
||||
import { useAppUrl } from '../../../../../common/lib/kibana/hooks';
|
||||
import { PolicyDetailsRouteState } from '../../../../../../common/endpoint/types';
|
||||
import { getPolicyDetailPath, getPolicyTrustedAppsPath } from '../../../../common/routing';
|
||||
import { PolicyDetailsForm } from '../policy_details_form';
|
||||
import { AppAction } from '../../../../../common/store/actions';
|
||||
import { usePolicyDetailsSelector } from '../policy_hooks';
|
||||
import { policyDetailsForUpdate } from '../../store/policy_details/selectors';
|
||||
|
||||
import { FleetTrustedAppsCard } from './endpoint_package_custom_extension/components/fleet_trusted_apps_card';
|
||||
import { LinkWithIcon } from './endpoint_package_custom_extension/components/link_with_icon';
|
||||
/**
|
||||
* Exports Endpoint-specific package policy instructions
|
||||
* for use in the Ingest app create / edit package policy
|
||||
|
@ -40,7 +48,12 @@ const WrappedPolicyDetailsForm = memo<{
|
|||
}>(({ policyId, onChange }) => {
|
||||
const dispatch = useDispatch<(a: AppAction) => void>();
|
||||
const updatedPolicy = usePolicyDetailsSelector(policyDetailsForUpdate);
|
||||
const { getAppUrl } = useAppUrl();
|
||||
const [, setLastUpdatedPolicy] = useState(updatedPolicy);
|
||||
// TODO: Remove this and related code when removing FF
|
||||
const isTrustedAppsByPolicyEnabled = useIsExperimentalFeatureEnabled(
|
||||
'trustedAppsByPolicyEnabled'
|
||||
);
|
||||
|
||||
// When the form is initially displayed, trigger the Redux middleware which is based on
|
||||
// the location information stored via the `userChangedUrl` action.
|
||||
|
@ -93,9 +106,91 @@ const WrappedPolicyDetailsForm = memo<{
|
|||
});
|
||||
}, [onChange, updatedPolicy]);
|
||||
|
||||
const policyTrustedAppsPath = useMemo(() => getPolicyTrustedAppsPath(policyId), [policyId]);
|
||||
const policyTrustedAppRouteState = useMemo<PolicyDetailsRouteState>(() => {
|
||||
const fleetPackageIntegrationCustomUrlPath = `#${
|
||||
pagePathGetters.integration_policy_edit({ packagePolicyId: policyId })[1]
|
||||
}`;
|
||||
|
||||
return {
|
||||
backLink: {
|
||||
label: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.artifacts.backButtonLabel',
|
||||
{
|
||||
defaultMessage: `Back to Fleet integration policy`,
|
||||
}
|
||||
),
|
||||
navigateTo: [
|
||||
INTEGRATIONS_PLUGIN_ID,
|
||||
{
|
||||
path: fleetPackageIntegrationCustomUrlPath,
|
||||
},
|
||||
],
|
||||
href: getAppUrl({
|
||||
appId: INTEGRATIONS_PLUGIN_ID,
|
||||
path: fleetPackageIntegrationCustomUrlPath,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}, [getAppUrl, policyId]);
|
||||
|
||||
const policyTrustedAppsLink = useMemo(
|
||||
() => (
|
||||
<LinkWithIcon
|
||||
href={getAppUrl({
|
||||
path: policyTrustedAppsPath,
|
||||
})}
|
||||
appPath={policyTrustedAppsPath}
|
||||
appState={policyTrustedAppRouteState}
|
||||
data-test-subj="linkToTrustedApps"
|
||||
size="m"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.fleetCustomExtension.manageTrustedAppLinkLabel"
|
||||
defaultMessage="Manage trusted applications"
|
||||
/>
|
||||
</LinkWithIcon>
|
||||
),
|
||||
[getAppUrl, policyTrustedAppsPath, policyTrustedAppRouteState]
|
||||
);
|
||||
|
||||
return (
|
||||
<div data-test-subj="endpointIntegrationPolicyForm">
|
||||
<PolicyDetailsForm />
|
||||
{isTrustedAppsByPolicyEnabled ? (
|
||||
<>
|
||||
<div>
|
||||
<EuiText>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetails.artifacts.title"
|
||||
defaultMessage="Artifacts"
|
||||
/>
|
||||
</h5>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<FleetTrustedAppsCard
|
||||
policyId={policyId}
|
||||
cardSize="m"
|
||||
customLink={policyTrustedAppsLink}
|
||||
/>
|
||||
</div>
|
||||
<EuiSpacer size="l" />
|
||||
<div>
|
||||
<EuiText>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetails.settings.title"
|
||||
defaultMessage="Policy settings"
|
||||
/>
|
||||
</h5>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<PolicyDetailsForm />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<PolicyDetailsForm />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -13,6 +13,8 @@ import { CurrentLicense } from '../../../../../common/components/current_license
|
|||
import { StartPlugins } from '../../../../../types';
|
||||
import { managementReducer } from '../../../../store/reducer';
|
||||
import { managementMiddlewareFactory } from '../../../../store/middleware';
|
||||
import { appReducer } from '../../../../../common/store/app';
|
||||
import { ExperimentalFeaturesService } from '../../../../../common/experimental_features_service';
|
||||
|
||||
type ComposeType = typeof compose;
|
||||
declare global {
|
||||
|
@ -51,8 +53,15 @@ export const withSecurityContext = <P extends {}>({
|
|||
store = createStore(
|
||||
combineReducers({
|
||||
management: managementReducer,
|
||||
app: appReducer,
|
||||
}),
|
||||
{ management: undefined },
|
||||
{
|
||||
management: undefined,
|
||||
// @ts-ignore ignore this error as we just need the enableExperimental and it's temporary
|
||||
app: {
|
||||
enableExperimental: ExperimentalFeaturesService.get(),
|
||||
},
|
||||
},
|
||||
composeEnhancers(applyMiddleware(...managementMiddlewareFactory(coreStart, depsStart)))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
PutTrustedAppsRequestParams,
|
||||
GetOneTrustedAppRequestParams,
|
||||
GetOneTrustedAppResponse,
|
||||
GetTrustedAppsSummaryRequest,
|
||||
} from '../../../../../common/endpoint/types/trusted_apps';
|
||||
import { resolvePathVariables } from '../../../../common/utils/resolve_path_variables';
|
||||
|
||||
|
@ -82,8 +83,10 @@ export class TrustedAppsHttpService implements TrustedAppsService {
|
|||
);
|
||||
}
|
||||
|
||||
async getTrustedAppsSummary() {
|
||||
return this.http.get<GetTrustedAppsSummaryResponse>(TRUSTED_APPS_SUMMARY_API);
|
||||
async getTrustedAppsSummary(request: GetTrustedAppsSummaryRequest) {
|
||||
return this.http.get<GetTrustedAppsSummaryResponse>(TRUSTED_APPS_SUMMARY_API, {
|
||||
query: request,
|
||||
});
|
||||
}
|
||||
|
||||
getPolicyList(options?: Parameters<typeof sendGetEndpointSpecificPackagePolicies>[1]) {
|
||||
|
|
|
@ -53,6 +53,7 @@ import {
|
|||
import { SecurityAppStore } from './common/store/store';
|
||||
import { licenseService } from './common/hooks/use_license';
|
||||
import { SecuritySolutionUiConfigType } from './common/types';
|
||||
import { ExperimentalFeaturesService } from './common/experimental_features_service';
|
||||
|
||||
import { getLazyEndpointPolicyEditExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension';
|
||||
import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension';
|
||||
|
@ -184,6 +185,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
|
||||
public start(core: CoreStart, plugins: StartPlugins) {
|
||||
KibanaServices.init({ ...core, ...plugins, kibanaVersion: this.kibanaVersion });
|
||||
ExperimentalFeaturesService.init({ experimentalFeatures: this.experimentalFeatures });
|
||||
if (plugins.fleet) {
|
||||
const { registerExtension } = plugins.fleet;
|
||||
|
||||
|
|
|
@ -879,6 +879,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
);
|
||||
expect(await testSubjects.isSelected('policyWindowsEvent_dns')).to.be(wasSelected);
|
||||
});
|
||||
|
||||
it('should show trusted apps card and link should go back to policy', async () => {
|
||||
await testSubjects.existOrFail('fleetTrustedAppsCard');
|
||||
await (await testSubjects.find('linkToTrustedApps')).click();
|
||||
await testSubjects.existOrFail('policyDetailsPage');
|
||||
await (await testSubjects.find('policyDetailsBackLink')).click();
|
||||
await testSubjects.existOrFail('endpointIntegrationPolicyForm');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
// always install Endpoint package by default when Fleet sets up
|
||||
`--xpack.fleet.packages.0.name=endpoint`,
|
||||
`--xpack.fleet.packages.0.version=latest`,
|
||||
// TODO: Remove feature flags once we're good to go
|
||||
'--xpack.securitySolution.enableExperimental=["trustedAppsByPolicyEnabled"]',
|
||||
],
|
||||
},
|
||||
layout: {
|
||||
|
|
Loading…
Reference in a new issue