Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security_Solution][Endpoint] Register Custom tab into Fleet Endpoint Integration Detail #85643

Merged
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fe1af85
Register UI Extension with fleet for endpoint custom content
paul-tavares Dec 9, 2020
7be498e
Fleet: add component props to the Package Custom UI extension
paul-tavares Dec 10, 2020
6de3686
Endpoint: initial UI for Trusted Apps custom entry
paul-tavares Dec 10, 2020
5bbbbba
Merge remote-tracking branch 'upstream/master' into task/olm-512-cust…
paul-tavares Dec 10, 2020
c874789
Fleet: fix up pkgkey generation in epm detials
paul-tavares Dec 10, 2020
0a3c98f
Fleet: epm detail content show full width for custom tab
paul-tavares Dec 10, 2020
e82498b
UI for link to trusted apps
paul-tavares Dec 10, 2020
d82ab72
Move custom extension to its own dir
paul-tavares Dec 10, 2020
d3bc7e6
Merge remote-tracking branch 'upstream/master' into task/olm-512-cust…
paul-tavares Dec 10, 2020
e81be37
Refactor to have each card separate
paul-tavares Dec 10, 2020
596542a
more refactor
paul-tavares Dec 10, 2020
9b5603a
Styled trusted app card items summary
paul-tavares Dec 10, 2020
9e6fe85
delete package policy edit page callout
paul-tavares Dec 10, 2020
5fdb9d0
remove edit package policy callout tests
paul-tavares Dec 10, 2020
519acfc
Functional test cases for custom tab
paul-tavares Dec 10, 2020
d62c139
fix i18n ids
paul-tavares Dec 10, 2020
b0075db
adjust flex grid
paul-tavares Dec 10, 2020
df27607
Refactor props for Content component
paul-tavares Dec 14, 2020
2c78fa7
Merge remote-tracking branch 'upstream/master' into task/olm-512-cust…
paul-tavares Dec 14, 2020
f78f8fe
Fix FTR fleet public imports
paul-tavares Dec 14, 2020
61162b2
Merge remote-tracking branch 'upstream/master' into task/olm-512-cust…
paul-tavares Dec 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
*/

import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import React from 'react';
import React, { useMemo } from 'react';
import styled from 'styled-components';
import { Redirect } from 'react-router-dom';
import { DetailParams } from '.';
import { PackageInfo } from '../../../../types';
import { DetailViewPanelName, PackageInfo } from '../../../../types';
import { AssetsFacetGroup } from '../../components/assets_facet_group';
import { CenterColumn, LeftColumn, RightColumn } from './layout';
import { OverviewPanel } from './overview_panel';
Expand All @@ -18,6 +18,7 @@ import { SettingsPanel } from './settings_panel';
import { useUIExtension } from '../../../../hooks/use_ui_extension';
import { ExtensionWrapper } from '../../../../components/extension_wrapper';
import { useLink } from '../../../../hooks';
import { pkgKeyFromPackageInfo } from '../../../../services/pkg_key_from_package_info';

type ContentProps = PackageInfo & Pick<DetailParams, 'panel'>;

Expand All @@ -34,7 +35,12 @@ const ContentFlexGroup = styled(EuiFlexGroup)`
`;

export function Content(props: ContentProps) {
const showRightColumn = props.panel !== 'policies';
const { panel } = props;
const showRightColumn = useMemo(() => {
const fullWidthContentPages: DetailViewPanelName[] = ['policies', 'custom'];
return !fullWidthContentPages.includes(panel!);
}, [panel]);

return (
<ContentFlexGroup>
<LeftSideColumn {...(!showRightColumn ? { columnGrow: 1 } : undefined)} />
Expand All @@ -52,7 +58,10 @@ export function Content(props: ContentProps) {

type ContentPanelProps = PackageInfo & Pick<DetailParams, 'panel'>;
export function ContentPanel(props: ContentPanelProps) {
const { panel, name, version, assets, title, removable, latestVersion } = props;
const { panel, ...packageInfo } = props;
const { name, version, assets, title, removable, latestVersion } = props;
const pkgkey = pkgKeyFromPackageInfo(packageInfo);

const CustomView = useUIExtension(name, 'package-detail-custom');
const { getPath } = useLink();

Expand All @@ -73,10 +82,10 @@ export function ContentPanel(props: ContentPanelProps) {
case 'custom':
return CustomView ? (
<ExtensionWrapper>
<CustomView />
<CustomView pkgkey={pkgkey} packageInfo={packageInfo} />
</ExtensionWrapper>
) : (
<Redirect to={getPath('integration_details', { pkgkey: `${name}-${version}` })} />
<Redirect to={getPath('integration_details', { pkgkey })} />
);
case 'overview':
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { ComponentType, LazyExoticComponent } from 'react';
import { NewPackagePolicy, PackagePolicy } from './index';
import { NewPackagePolicy, PackageInfo, PackagePolicy } from './index';

/** Register a Fleet UI extension */
export type UIExtensionRegistrationCallback = (extensionPoint: UIExtensionPoint) => void;
Expand Down Expand Up @@ -80,7 +80,13 @@ export interface PackagePolicyCreateExtension {
/**
* UI Component Extension is used to display a Custom tab (and view) under a given Integration
*/
export type PackageCustomExtensionComponent = ComponentType;
export type PackageCustomExtensionComponent = ComponentType<PackageCustomExtensionComponentProps>;

export interface PackageCustomExtensionComponentProps {
/** The package key value that should be used used for URLs */
pkgkey: string;
packageInfo: PackageInfo;
}

/** Extension point registration contract for Integration details Custom view */
export interface PackageCustomExtension {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export * from './applications/fleet/types/intra_app_route_state';
export * from './applications/fleet/types/ui_extensions';

export { pagePathGetters } from './applications/fleet/constants';
export { pkgKeyFromPackageInfo } from './applications/fleet/services/pkg_key_from_package_info';
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React, { memo, MouseEventHandler } from 'react';
import { EuiLink, EuiLinkProps, EuiButton, EuiButtonProps } from '@elastic/eui';
import { useNavigateToAppEventHandler } from '../../hooks/endpoint/use_navigate_to_app_event_handler';

type LinkToAppProps = (EuiLinkProps | EuiButtonProps) & {
export type LinkToAppProps = (EuiLinkProps | EuiButtonProps) & {
/** the app id - normally the value of the `id` in that plugin's `kibana.json` */
appId: string;
/** Any app specific path (route) */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { memo, useMemo } from 'react';
import { ApplicationStart } from 'kibana/public';
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
PackageCustomExtensionComponentProps,
pagePathGetters,
} from '../../../../../../../../../fleet/public';
import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public';
import { getTrustedAppsListPath } from '../../../../../../common/routing';
import { TrustedAppsListPageRouteState } from '../../../../../../../../common/endpoint/types';
import { PLUGIN_ID as FLEET_PLUGIN_ID } from '../../../../../../../../../fleet/common';
import { MANAGEMENT_APP_ID } from '../../../../../../common/constants';
import { LinkWithIcon } from './link_with_icon';
import { TrustedAppItemsSummary } from './trusted_app_items_summary';

export const FleetTrustedAppsCard = memo<PackageCustomExtensionComponentProps>(({ pkgkey }) => {
const {
services: {
application: { getUrlForApp },
},
} = useKibana<{ application: ApplicationStart }>();

const trustedAppsListUrlPath = getTrustedAppsListPath();

const trustedAppRouteState = useMemo<TrustedAppsListPageRouteState>(() => {
const fleetPackageCustomUrlPath = `#${pagePathGetters.integration_details({
pkgkey,
panel: 'custom',
})}`;
return {
backButtonLabel: i18n.translate(
'xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel',
{ defaultMessage: 'Back to Endpoint Integration' }
),
onBackButtonNavigateTo: [
FLEET_PLUGIN_ID,
{
path: fleetPackageCustomUrlPath,
},
],
backButtonUrl: getUrlForApp(FLEET_PLUGIN_ID, {
path: fleetPackageCustomUrlPath,
}),
};
}, [getUrlForApp, pkgkey]);

return (
<EuiPanel paddingSize="l">
<EuiFlexGroup alignItems="baseline">
<EuiFlexItem>
<EuiText>
<h4>
<FormattedMessage
id="xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppsLabel"
defaultMessage="Trusted Applications"
/>
</h4>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<TrustedAppItemsSummary />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<span>
<LinkWithIcon
appId={MANAGEMENT_APP_ID}
href={getUrlForApp(MANAGEMENT_APP_ID, { path: trustedAppsListUrlPath })}
appPath={trustedAppsListUrlPath}
appState={trustedAppRouteState}
data-test-subj="linkToTrustedApps"
>
<FormattedMessage
id="xpack.securitySolution.endpoint.fleetCustomExtension.manageTrustedAppLinkLabel"
defaultMessage="Manage trusted applications"
/>
</LinkWithIcon>
</span>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
});

FleetTrustedAppsCard.displayName = 'FleetTrustedAppsCard';
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import styled from 'styled-components';
import React, { FC, memo } from 'react';
import { EuiIcon } from '@elastic/eui';
import {
LinkToApp,
LinkToAppProps,
} from '../../../../../../../common/components/endpoint/link_to_app';

const LinkLabel = styled.span`
display: inline-block;
padding-right: ${(props) => props.theme.eui.paddingSizes.s};
`;

export const LinkWithIcon: FC<LinkToAppProps> = memo(({ children, ...props }) => {
return (
<LinkToApp {...props}>
<LinkLabel>{children}</LinkLabel>
<EuiIcon type="popout" />
</LinkToApp>
);
});

LinkWithIcon.displayName = 'LinkWithIcon';
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import React, { FC, memo, useEffect, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { CoreStart } from 'kibana/public';
import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public';
import { TrustedAppsHttpService } from '../../../../../trusted_apps/service';

export const TrustedAppItemsSummary = memo(() => {
const {
services: { http },
} = useKibana<CoreStart>();
const [total, setTotal] = useState<number>(0);
const [trustedAppsApi] = useState(() => new TrustedAppsHttpService(http));

useEffect(() => {
trustedAppsApi
.getTrustedAppsList({
page: 1,
per_page: 1,
})
.then((response) => {
setTotal(response.total);
});
}, [trustedAppsApi]);

return (
<div>
<SummaryStat value={total} color="primary">
<FormattedMessage
id="xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.totalLabel"
defaultMessage="Total"
/>
</SummaryStat>
</div>
);
});

TrustedAppItemsSummary.displayName = 'TrustedAppItemsSummary';

const SummaryStat: FC<{ value: number; color?: EuiBadgeProps['color'] }> = memo(
({ children, value, color, ...commonProps }) => {
return (
<EuiText className="eui-displayInlineBlock" size="s">
<EuiFlexGroup
responsive={false}
justifyContent="center"
direction="row"
alignItems="center"
>
<EuiFlexItem grow={false} style={{ fontWeight: 'bold' }}>
{children}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBadge color={color}>{value}</EuiBadge>
</EuiFlexItem>
</EuiFlexGroup>
</EuiText>
);
}
);

SummaryStat.displayName = 'SummaryState';
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { memo } from 'react';
import { PackageCustomExtensionComponentProps } from '../../../../../../../../fleet/public';
import { FleetTrustedAppsCard } from './components/fleet_trusted_apps_card';

export const EndpointPackageCustomExtension = memo<PackageCustomExtensionComponentProps>(
(props) => {
return (
<div data-test-subj="fleetEndpointPackageCustomContent">
<FleetTrustedAppsCard {...props} />
</div>
);
}
);

EndpointPackageCustomExtension.displayName = 'EndpointPackageCustomExtension';
Loading