Skip to content

Commit

Permalink
[Security_Solution][Endpoint] Register Custom tab into Fleet Endpoint…
Browse files Browse the repository at this point in the history
… Integration Detail (#85643)

* Fleet: add component props to the Package Custom UI extension
* Endpoint: Register UI Extension with fleet for endpoint custom content
* Endpoint: UI for Trusted Apps custom entry
  • Loading branch information
paul-tavares authored Dec 14, 2020
1 parent 504c873 commit f6cd264
Show file tree
Hide file tree
Showing 17 changed files with 367 additions and 176 deletions.
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, { memo, 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,12 +35,17 @@ 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)} />
<CenterColumn {...(!showRightColumn ? { columnGrow: 6 } : undefined)}>
<ContentPanel {...props} />
<ContentPanel panel={panel!} packageInfo={props} />
</CenterColumn>
{showRightColumn && (
<RightColumn>
Expand All @@ -50,9 +56,14 @@ 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;
interface ContentPanelProps {
packageInfo: PackageInfo;
panel: DetailViewPanelName;
}
export const ContentPanel = memo<ContentPanelProps>(({ panel, packageInfo }) => {
const { name, version, assets, title, removable, latestVersion } = packageInfo;
const pkgkey = pkgKeyFromPackageInfo(packageInfo);

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

Expand All @@ -73,16 +84,16 @@ 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:
return <OverviewPanel {...props} />;
return <OverviewPanel {...packageInfo} />;
}
}
});

type RightColumnContentProps = PackageInfo & Pick<DetailParams, 'panel'>;
function RightColumnContent(props: RightColumnContentProps) {
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

0 comments on commit f6cd264

Please sign in to comment.