mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Fleet] Revamp integration detail page (#90887)
* Extract integration detail page changes from POC * Remove unneccessary link wrappers * Remove unused import * Fix method name * Fix linting
This commit is contained in:
parent
f3debcd084
commit
c2b41c484b
40 changed files with 900 additions and 758 deletions
|
@ -37,7 +37,7 @@ export interface HeaderProps {
|
|||
leftColumn?: JSX.Element;
|
||||
rightColumn?: JSX.Element;
|
||||
rightColumnGrow?: EuiFlexItemProps['grow'];
|
||||
tabs?: EuiTabProps[];
|
||||
tabs?: Array<Omit<EuiTabProps, 'name'> & { name?: JSX.Element | string }>;
|
||||
tabsClassName?: string;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ export const Header: React.FC<HeaderProps> = ({
|
|||
<EuiSpacer size="s" />
|
||||
<Tabs className={tabsClassName}>
|
||||
{tabs.map((props) => (
|
||||
<EuiTab {...props} key={props.id}>
|
||||
<EuiTab {...(props as EuiTabProps)} key={props.id}>
|
||||
{props.name}
|
||||
</EuiTab>
|
||||
))}
|
||||
|
|
|
@ -43,7 +43,7 @@ const TutorialModuleNotice: TutorialModuleNoticeComponent = memo(({ moduleName }
|
|||
),
|
||||
availableAsIntegrationLink: (
|
||||
<EuiLink
|
||||
href={getHref('integration_details', {
|
||||
href={getHref('integration_details_overview', {
|
||||
pkgkey: pkgKeyFromPackageInfo(pkgInfo),
|
||||
})}
|
||||
>
|
||||
|
|
|
@ -18,7 +18,10 @@ export type StaticPage =
|
|||
| 'data_streams';
|
||||
|
||||
export type DynamicPage =
|
||||
| 'integration_details'
|
||||
| 'integration_details_overview'
|
||||
| 'integration_details_policies'
|
||||
| 'integration_details_settings'
|
||||
| 'integration_details_custom'
|
||||
| 'integration_policy_edit'
|
||||
| 'policy_details'
|
||||
| 'add_integration_from_policy'
|
||||
|
@ -43,6 +46,10 @@ export const PAGE_ROUTING_PATHS = {
|
|||
integrations_all: '/integrations',
|
||||
integrations_installed: '/integrations/installed',
|
||||
integration_details: '/integrations/detail/:pkgkey/:panel?',
|
||||
integration_details_overview: '/integrations/detail/:pkgkey/overview',
|
||||
integration_details_policies: '/integrations/detail/:pkgkey/policies',
|
||||
integration_details_settings: '/integrations/detail/:pkgkey/settings',
|
||||
integration_details_custom: '/integrations/detail/:pkgkey/custom',
|
||||
integration_policy_edit: '/integrations/edit-integration/:packagePolicyId',
|
||||
policies: '/policies',
|
||||
policies_list: '/policies',
|
||||
|
@ -70,8 +77,10 @@ export const pagePathGetters: {
|
|||
integrations: () => '/integrations',
|
||||
integrations_all: () => '/integrations',
|
||||
integrations_installed: () => '/integrations/installed',
|
||||
integration_details: ({ pkgkey, panel }) =>
|
||||
`/integrations/detail/${pkgkey}${panel ? `/${panel}` : ''}`,
|
||||
integration_details_overview: ({ pkgkey }) => `/integrations/detail/${pkgkey}/overview`,
|
||||
integration_details_policies: ({ pkgkey }) => `/integrations/detail/${pkgkey}/policies`,
|
||||
integration_details_settings: ({ pkgkey }) => `/integrations/detail/${pkgkey}/settings`,
|
||||
integration_details_custom: ({ pkgkey }) => `/integrations/detail/${pkgkey}/custom`,
|
||||
integration_policy_edit: ({ packagePolicyId }) =>
|
||||
`/integrations/edit-integration/${packagePolicyId}`,
|
||||
policies: () => '/policies',
|
||||
|
|
|
@ -18,7 +18,7 @@ const BASE_BREADCRUMB: ChromeBreadcrumb = {
|
|||
};
|
||||
|
||||
const breadcrumbGetters: {
|
||||
[key in Page]: (values: DynamicPagePathValues) => ChromeBreadcrumb[];
|
||||
[key in Page]?: (values: DynamicPagePathValues) => ChromeBreadcrumb[];
|
||||
} = {
|
||||
base: () => [BASE_BREADCRUMB],
|
||||
overview: () => [
|
||||
|
@ -65,7 +65,7 @@ const breadcrumbGetters: {
|
|||
}),
|
||||
},
|
||||
],
|
||||
integration_details: ({ pkgTitle }) => [
|
||||
integration_details_overview: ({ pkgTitle }) => [
|
||||
BASE_BREADCRUMB,
|
||||
{
|
||||
href: pagePathGetters.integrations(),
|
||||
|
@ -84,7 +84,7 @@ const breadcrumbGetters: {
|
|||
}),
|
||||
},
|
||||
{
|
||||
href: pagePathGetters.integration_details({ pkgkey, panel: 'policies' }),
|
||||
href: pagePathGetters.integration_details_policies({ pkgkey }),
|
||||
text: pkgTitle,
|
||||
},
|
||||
{ text: policyName },
|
||||
|
@ -142,7 +142,7 @@ const breadcrumbGetters: {
|
|||
}),
|
||||
},
|
||||
{
|
||||
href: pagePathGetters.integration_details({ pkgkey }),
|
||||
href: pagePathGetters.integration_details_overview({ pkgkey }),
|
||||
text: pkgTitle,
|
||||
},
|
||||
{
|
||||
|
@ -221,10 +221,11 @@ const breadcrumbGetters: {
|
|||
|
||||
export function useBreadcrumbs(page: Page, values: DynamicPagePathValues = {}) {
|
||||
const { chrome, http } = useStartServices();
|
||||
const breadcrumbs: ChromeBreadcrumb[] = breadcrumbGetters[page](values).map((breadcrumb) => ({
|
||||
...breadcrumb,
|
||||
href: breadcrumb.href ? http.basePath.prepend(`${BASE_PATH}#${breadcrumb.href}`) : undefined,
|
||||
}));
|
||||
const breadcrumbs: ChromeBreadcrumb[] =
|
||||
breadcrumbGetters[page]?.(values).map((breadcrumb) => ({
|
||||
...breadcrumb,
|
||||
href: breadcrumb.href ? http.basePath.prepend(`${BASE_PATH}#${breadcrumb.href}`) : undefined,
|
||||
})) || [];
|
||||
const docTitle: string[] = [...breadcrumbs]
|
||||
.reverse()
|
||||
.map((breadcrumb) => breadcrumb.text as string);
|
||||
|
|
|
@ -217,7 +217,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
}
|
||||
return from === 'policy'
|
||||
? getHref('policy_details', { policyId: agentPolicyId || policyId })
|
||||
: getHref('integration_details', { pkgkey });
|
||||
: getHref('integration_details_overview', { pkgkey });
|
||||
}, [agentPolicyId, policyId, from, getHref, pkgkey, routeState]);
|
||||
|
||||
const cancelClickHandler: ReactEventHandler = useCallback(
|
||||
|
|
|
@ -246,9 +246,8 @@ export const EditPackagePolicyForm = memo<{
|
|||
const cancelUrl = useMemo((): string => {
|
||||
if (packageInfo && policyId) {
|
||||
return from === 'package-edit'
|
||||
? getHref('integration_details', {
|
||||
? getHref('integration_details_policies', {
|
||||
pkgkey: pkgKeyFromPackageInfo(packageInfo!),
|
||||
panel: 'policies',
|
||||
})
|
||||
: getHref('policy_details', { policyId });
|
||||
}
|
||||
|
@ -258,9 +257,8 @@ export const EditPackagePolicyForm = memo<{
|
|||
const successRedirectPath = useMemo(() => {
|
||||
if (packageInfo && policyId) {
|
||||
return from === 'package-edit'
|
||||
? getPath('integration_details', {
|
||||
? getPath('integration_details_policies', {
|
||||
pkgkey: pkgKeyFromPackageInfo(packageInfo!),
|
||||
panel: 'policies',
|
||||
})
|
||||
: getPath('policy_details', { policyId });
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ export function PackageCard({
|
|||
title={title || ''}
|
||||
description={description}
|
||||
icon={<PackageIcon icons={icons} packageName={name} version={version} size="xl" />}
|
||||
href={getHref('integration_details', { pkgkey: `${name}-${urlVersion}` })}
|
||||
href={getHref('integration_details_overview', { pkgkey: `${name}-${urlVersion}` })}
|
||||
betaBadgeLabel={release && release !== 'ga' ? RELEASE_BADGE_LABEL[release] : undefined}
|
||||
betaBadgeTooltipContent={
|
||||
release && release !== 'ga' ? RELEASE_BADGE_DESCRIPTION[release] : undefined
|
||||
|
|
|
@ -90,9 +90,8 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar
|
|||
} else {
|
||||
setPackageInstallStatus({ name, status: InstallStatus.installed, version });
|
||||
if (fromUpdate) {
|
||||
const settingsPath = getPath('integration_details', {
|
||||
const settingsPath = getPath('integration_details_settings', {
|
||||
pkgkey: `${name}-${version}`,
|
||||
panel: 'settings',
|
||||
});
|
||||
history.push(settingsPath);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiIcon, EuiPanel } from '@elastic/eui';
|
||||
import { usePackageIconType, UsePackageIconType } from '../../../../../hooks';
|
||||
import { Loading } from '../../../../../components';
|
||||
|
||||
const PanelWrapper = styled.div`
|
||||
// NOTE: changes to the width here will impact navigation tabs page layout under integration package details
|
||||
width: ${(props) =>
|
||||
parseFloat(props.theme.eui.euiSize) * 6 + parseFloat(props.theme.eui.euiSizeXL) * 2}px;
|
||||
height: 1px;
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
const Panel = styled(EuiPanel)`
|
||||
padding: ${(props) => props.theme.eui.spacerSizes.xl};
|
||||
margin-bottom: -100%;
|
||||
svg,
|
||||
img {
|
||||
height: ${(props) => parseFloat(props.theme.eui.euiSize) * 6}px;
|
||||
width: ${(props) => parseFloat(props.theme.eui.euiSize) * 6}px;
|
||||
}
|
||||
.euiFlexItem {
|
||||
height: ${(props) => parseFloat(props.theme.eui.euiSize) * 6}px;
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export function IconPanel({
|
||||
packageName,
|
||||
version,
|
||||
icons,
|
||||
}: Pick<UsePackageIconType, 'packageName' | 'version' | 'icons'>) {
|
||||
const iconType = usePackageIconType({ packageName, version, icons });
|
||||
|
||||
return (
|
||||
<PanelWrapper>
|
||||
<Panel>
|
||||
<EuiIcon type={iconType} size="original" />
|
||||
</Panel>
|
||||
</PanelWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export function LoadingIconPanel() {
|
||||
return (
|
||||
<PanelWrapper>
|
||||
<Panel>
|
||||
<Loading />
|
||||
</Panel>
|
||||
</PanelWrapper>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
export { UpdateIcon } from './update_icon';
|
||||
export { IntegrationAgentPolicyCount } from './integration_agent_policy_count';
|
||||
export { IconPanel, LoadingIconPanel } from './icon_panel';
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { useGetPackageStats } from '../../../../hooks';
|
||||
import { useGetPackageStats } from '../../../../../hooks';
|
||||
|
||||
/**
|
||||
* Displays a count of Agent Policies that are using the given integration
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { EuiIconTip, EuiIconProps } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const UpdateIcon = ({ size = 'm' }: { size?: EuiIconProps['size'] }) => (
|
||||
<EuiIconTip
|
||||
aria-label={i18n.translate('xpack.fleet.epm.updateAvailableTooltip', {
|
||||
defaultMessage: 'Update available',
|
||||
})}
|
||||
size={size}
|
||||
type="alert"
|
||||
color="warning"
|
||||
content={i18n.translate('xpack.fleet.epm.updateAvailableTooltip', {
|
||||
defaultMessage: 'Update available',
|
||||
})}
|
||||
/>
|
||||
);
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { DetailParams } from '.';
|
||||
import { DetailViewPanelName, PackageInfo } from '../../../../types';
|
||||
import { AssetsFacetGroup } from '../../components/assets_facet_group';
|
||||
import { CenterColumn, LeftColumn, RightColumn } from './layout';
|
||||
import { OverviewPanel } from './overview_panel';
|
||||
import { PackagePoliciesPanel } from './package_policies_panel';
|
||||
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'>;
|
||||
|
||||
const LeftSideColumn = styled(LeftColumn)`
|
||||
/* 🤢🤷 https://www.styled-components.com/docs/faqs#how-can-i-override-styles-with-higher-specificity */
|
||||
&&& {
|
||||
margin-top: 77px;
|
||||
}
|
||||
`;
|
||||
|
||||
// fixes IE11 problem with nested flex items
|
||||
const ContentFlexGroup = styled(EuiFlexGroup)`
|
||||
flex: 0 0 auto !important;
|
||||
`;
|
||||
|
||||
export function Content(props: ContentProps) {
|
||||
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 panel={panel!} packageInfo={props} />
|
||||
</CenterColumn>
|
||||
{showRightColumn && (
|
||||
<RightColumn>
|
||||
<RightColumnContent {...props} />
|
||||
</RightColumn>
|
||||
)}
|
||||
</ContentFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
switch (panel) {
|
||||
case 'settings':
|
||||
return (
|
||||
<SettingsPanel
|
||||
name={name}
|
||||
version={version}
|
||||
assets={assets}
|
||||
title={title}
|
||||
removable={removable}
|
||||
latestVersion={latestVersion}
|
||||
/>
|
||||
);
|
||||
case 'policies':
|
||||
return <PackagePoliciesPanel name={name} version={version} />;
|
||||
case 'custom':
|
||||
return CustomView ? (
|
||||
<ExtensionWrapper>
|
||||
<CustomView pkgkey={pkgkey} packageInfo={packageInfo} />
|
||||
</ExtensionWrapper>
|
||||
) : (
|
||||
<Redirect to={getPath('integration_details', { pkgkey })} />
|
||||
);
|
||||
case 'overview':
|
||||
default:
|
||||
return <OverviewPanel {...packageInfo} />;
|
||||
}
|
||||
});
|
||||
|
||||
type RightColumnContentProps = PackageInfo & Pick<DetailParams, 'panel'>;
|
||||
function RightColumnContent(props: RightColumnContentProps) {
|
||||
const { assets, panel } = props;
|
||||
switch (panel) {
|
||||
case 'overview':
|
||||
return assets ? (
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssetsFacetGroup assets={assets} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : null;
|
||||
default:
|
||||
return <EuiSpacer />;
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* 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 { EuiButton, EuiButtonEmpty, EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
|
||||
import React, { Fragment, useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const BottomFade = styled.div`
|
||||
width: 100%;
|
||||
background: ${(props) =>
|
||||
`linear-gradient(${props.theme.eui.euiColorEmptyShade}00 0%, ${props.theme.eui.euiColorEmptyShade} 100%)`};
|
||||
margin-top: -${(props) => parseInt(props.theme.eui.spacerSizes.xl, 10) * 2}px;
|
||||
height: ${(props) => parseInt(props.theme.eui.spacerSizes.xl, 10) * 2}px;
|
||||
position: absolute;
|
||||
`;
|
||||
const ContentCollapseContainer = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
const CollapseButtonContainer = styled.div`
|
||||
display: inline-block;
|
||||
background-color: ${(props) => props.theme.eui.euiColorEmptyShade};
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
top: ${(props) => parseInt(props.theme.eui.euiButtonHeight, 10) / 2}px;
|
||||
`;
|
||||
const CollapseButtonTop = styled(EuiButtonEmpty)`
|
||||
float: right;
|
||||
`;
|
||||
|
||||
const CollapseButton = ({
|
||||
open,
|
||||
toggleCollapse,
|
||||
}: {
|
||||
open: boolean;
|
||||
toggleCollapse: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiHorizontalRule />
|
||||
<CollapseButtonContainer>
|
||||
<EuiButton onClick={toggleCollapse} iconType={`arrow${open ? 'Up' : 'Down'}`}>
|
||||
{open ? 'Collapse' : 'Read more'}
|
||||
</EuiButton>
|
||||
</CollapseButtonContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ContentCollapse = ({ children }: { children: React.ReactNode }) => {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [height, setHeight] = useState<number | string>('auto');
|
||||
const [collapsible, setCollapsible] = useState<boolean>(true);
|
||||
const contentEl = useRef<HTMLDivElement>(null);
|
||||
const collapsedHeight = 360;
|
||||
|
||||
// if content is too small, don't collapse
|
||||
useLayoutEffect(
|
||||
() =>
|
||||
contentEl.current && contentEl.current.clientHeight < collapsedHeight
|
||||
? setCollapsible(false)
|
||||
: setHeight(collapsedHeight),
|
||||
[]
|
||||
);
|
||||
|
||||
const clickOpen = useCallback(() => {
|
||||
setOpen(!open);
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{collapsible ? (
|
||||
<ContentCollapseContainer>
|
||||
<div
|
||||
ref={contentEl}
|
||||
style={{ height: `${open ? 'auto' : `${height}px`}`, overflow: 'hidden' }}
|
||||
>
|
||||
{open && (
|
||||
<CollapseButtonTop onClick={clickOpen} iconType={`arrow${open ? 'Up' : 'Down'}`}>
|
||||
Collapse
|
||||
</CollapseButtonTop>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
{!open && <BottomFade />}
|
||||
<CollapseButton open={open} toggleCollapse={clickOpen} />
|
||||
</ContentCollapseContainer>
|
||||
) : (
|
||||
<div>{children}</div>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { Redirect } from 'react-router-dom';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { useUIExtension } from '../../../../../hooks/use_ui_extension';
|
||||
import { useLink } from '../../../../../hooks';
|
||||
import { PackageInfo } from '../../../../../types';
|
||||
import { pkgKeyFromPackageInfo } from '../../../../../services/pkg_key_from_package_info';
|
||||
import { ExtensionWrapper } from '../../../../../components/extension_wrapper';
|
||||
|
||||
interface Props {
|
||||
packageInfo: PackageInfo;
|
||||
}
|
||||
|
||||
export const CustomViewPage: React.FC<Props> = memo(({ packageInfo }) => {
|
||||
const CustomView = useUIExtension(packageInfo.name, 'package-detail-custom');
|
||||
const { getPath } = useLink();
|
||||
const pkgkey = useMemo(() => pkgKeyFromPackageInfo(packageInfo), [packageInfo]);
|
||||
|
||||
return CustomView ? (
|
||||
<EuiFlexGroup alignItems="flexStart">
|
||||
<EuiFlexItem grow={1} />
|
||||
<EuiFlexItem grow={6}>
|
||||
<ExtensionWrapper>
|
||||
<CustomView pkgkey={pkgkey} packageInfo={packageInfo} />
|
||||
</ExtensionWrapper>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<Redirect to={getPath('integration_details_overview', { pkgkey })} />
|
||||
);
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
export { CustomViewPage } from './custom';
|
|
@ -28,7 +28,7 @@ import { act, cleanup } from '@testing-library/react';
|
|||
|
||||
describe('when on integration detail', () => {
|
||||
const pkgkey = 'nginx-0.3.7';
|
||||
const detailPageUrlPath = pagePathGetters.integration_details({ pkgkey });
|
||||
const detailPageUrlPath = pagePathGetters.integration_details_overview({ pkgkey });
|
||||
let testRenderer: TestRenderer;
|
||||
let renderResult: ReturnType<typeof testRenderer.render>;
|
||||
let mockedApi: MockedApi<EpmPackageDetailsResponseProvidersMock>;
|
||||
|
@ -100,7 +100,7 @@ describe('when on integration detail', () => {
|
|||
it('should redirect if custom url is accessed', () => {
|
||||
act(() => {
|
||||
testRenderer.history.push(
|
||||
pagePathGetters.integration_details({ pkgkey: 'nginx-0.3.7', panel: 'custom' })
|
||||
pagePathGetters.integration_details_custom({ pkgkey: 'nginx-0.3.7' })
|
||||
);
|
||||
});
|
||||
expect(testRenderer.history.location.pathname).toEqual(detailPageUrlPath);
|
||||
|
@ -148,7 +148,7 @@ describe('when on integration detail', () => {
|
|||
it('should display custom content when tab is clicked', async () => {
|
||||
act(() => {
|
||||
testRenderer.history.push(
|
||||
pagePathGetters.integration_details({ pkgkey: 'nginx-0.3.7', panel: 'custom' })
|
||||
pagePathGetters.integration_details_custom({ pkgkey: 'nginx-0.3.7' })
|
||||
);
|
||||
});
|
||||
await lazyComponentWasRendered;
|
||||
|
@ -173,14 +173,14 @@ describe('when on integration detail', () => {
|
|||
onCancelNavigateTo: [
|
||||
'fleet',
|
||||
{
|
||||
path: '#/integrations/detail/nginx-0.3.7',
|
||||
path: '#/integrations/detail/nginx-0.3.7/overview',
|
||||
},
|
||||
],
|
||||
onCancelUrl: '#/integrations/detail/nginx-0.3.7',
|
||||
onCancelUrl: '#/integrations/detail/nginx-0.3.7/overview',
|
||||
onSaveNavigateTo: [
|
||||
'fleet',
|
||||
{
|
||||
path: '#/integrations/detail/nginx-0.3.7',
|
||||
path: '#/integrations/detail/nginx-0.3.7/overview',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -188,7 +188,7 @@ describe('when on integration detail', () => {
|
|||
});
|
||||
|
||||
describe('and on the Policies Tab', () => {
|
||||
const policiesTabURLPath = pagePathGetters.integration_details({ pkgkey, panel: 'policies' });
|
||||
const policiesTabURLPath = pagePathGetters.integration_details_policies({ pkgkey });
|
||||
beforeEach(() => {
|
||||
testRenderer.history.push(policiesTabURLPath);
|
||||
render();
|
||||
|
|
|
@ -4,72 +4,50 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useMemo, useCallback, ReactEventHandler } from 'react';
|
||||
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
||||
import React, { ReactEventHandler, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Redirect, Route, Switch, useHistory, useLocation, useParams } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiBetaBadge,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiDescriptionList,
|
||||
EuiDescriptionListTitle,
|
||||
EuiDescriptionListDescription,
|
||||
EuiDescriptionListTitle,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { useUIExtension } from '../../../../hooks/use_ui_extension';
|
||||
import { PAGE_ROUTING_PATHS, PLUGIN_ID } from '../../../../constants';
|
||||
import { useCapabilities, useGetPackageInfoByKey, useLink } from '../../../../hooks';
|
||||
import { pkgKeyFromPackageInfo } from '../../../../services/pkg_key_from_package_info';
|
||||
import {
|
||||
CreatePackagePolicyRouteState,
|
||||
DetailViewPanelName,
|
||||
entries,
|
||||
InstallStatus,
|
||||
PackageInfo,
|
||||
} from '../../../../types';
|
||||
import { Loading, Error } from '../../../../components';
|
||||
import {
|
||||
useGetPackageInfoByKey,
|
||||
useBreadcrumbs,
|
||||
useLink,
|
||||
useCapabilities,
|
||||
} from '../../../../hooks';
|
||||
import { Error, Loading } from '../../../../components';
|
||||
import { useBreadcrumbs } from '../../../../hooks';
|
||||
import { WithHeaderLayout, WithHeaderLayoutProps } from '../../../../layouts';
|
||||
import { RELEASE_BADGE_DESCRIPTION, RELEASE_BADGE_LABEL } from '../../components/release_badge';
|
||||
import { useSetPackageInstallStatus } from '../../hooks';
|
||||
import { IconPanel, LoadingIconPanel } from '../../components/icon_panel';
|
||||
import { RELEASE_BADGE_LABEL, RELEASE_BADGE_DESCRIPTION } from '../../components/release_badge';
|
||||
import { UpdateIcon } from '../../components/icons';
|
||||
import { Content } from './content';
|
||||
import { IntegrationAgentPolicyCount, UpdateIcon, IconPanel, LoadingIconPanel } from './components';
|
||||
import { OverviewPage } from './overview';
|
||||
import { PackagePoliciesPage } from './policies';
|
||||
import { SettingsPage } from './settings';
|
||||
import { CustomViewPage } from './custom';
|
||||
import './index.scss';
|
||||
import { useUIExtension } from '../../../../hooks/use_ui_extension';
|
||||
import { PLUGIN_ID } from '../../../../../../../common/constants';
|
||||
import { pkgKeyFromPackageInfo } from '../../../../services/pkg_key_from_package_info';
|
||||
import { IntegrationAgentPolicyCount } from './integration_agent_policy_count';
|
||||
|
||||
export const DEFAULT_PANEL: DetailViewPanelName = 'overview';
|
||||
|
||||
export interface DetailParams {
|
||||
pkgkey: string;
|
||||
panel?: DetailViewPanelName;
|
||||
}
|
||||
|
||||
const PanelDisplayNames: Record<DetailViewPanelName, string> = {
|
||||
overview: i18n.translate('xpack.fleet.epm.packageDetailsNav.overviewLinkText', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
policies: i18n.translate('xpack.fleet.epm.packageDetailsNav.packagePoliciesLinkText', {
|
||||
defaultMessage: 'Policies',
|
||||
}),
|
||||
settings: i18n.translate('xpack.fleet.epm.packageDetailsNav.settingsLinkText', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
custom: i18n.translate('xpack.fleet.epm.packageDetailsNav.packageCustomLinkText', {
|
||||
defaultMessage: 'Advanced',
|
||||
}),
|
||||
};
|
||||
|
||||
const Divider = styled.div`
|
||||
width: 0;
|
||||
height: 100%;
|
||||
|
@ -82,12 +60,12 @@ const FlexItemWithMinWidth = styled(EuiFlexItem)`
|
|||
`;
|
||||
|
||||
function Breadcrumbs({ packageTitle }: { packageTitle: string }) {
|
||||
useBreadcrumbs('integration_details', { pkgTitle: packageTitle });
|
||||
useBreadcrumbs('integration_details_overview', { pkgTitle: packageTitle });
|
||||
return null;
|
||||
}
|
||||
|
||||
export function Detail() {
|
||||
const { pkgkey, panel = DEFAULT_PANEL } = useParams<DetailParams>();
|
||||
const { pkgkey, panel } = useParams<DetailParams>();
|
||||
const { getHref, getPath } = useLink();
|
||||
const hasWriteCapabilites = useCapabilities().write;
|
||||
const history = useHistory();
|
||||
|
@ -247,7 +225,7 @@ export function Detail() {
|
|||
{ isDivider: true },
|
||||
{
|
||||
label: i18n.translate('xpack.fleet.epm.usedByLabel', {
|
||||
defaultMessage: 'Agent Policies',
|
||||
defaultMessage: 'Agent policies',
|
||||
}),
|
||||
'data-test-subj': 'agentPolicyCount',
|
||||
content: <IntegrationAgentPolicyCount packageName={packageInfo.name} />,
|
||||
|
@ -306,37 +284,79 @@ export function Detail() {
|
|||
]
|
||||
);
|
||||
|
||||
const tabs = useMemo<WithHeaderLayoutProps['tabs']>(() => {
|
||||
const headerTabs = useMemo<WithHeaderLayoutProps['tabs']>(() => {
|
||||
if (!packageInfo) {
|
||||
return [];
|
||||
}
|
||||
const packageInfoKey = pkgKeyFromPackageInfo(packageInfo);
|
||||
|
||||
return (entries(PanelDisplayNames)
|
||||
.filter(([panelId]) => {
|
||||
// Don't show `Policies` tab if package is not installed
|
||||
if (panelId === 'policies' && packageInstallStatus !== InstallStatus.installed) {
|
||||
return false;
|
||||
}
|
||||
const tabs: WithHeaderLayoutProps['tabs'] = [
|
||||
{
|
||||
id: 'overview',
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetailsNav.overviewLinkText"
|
||||
defaultMessage="Overview"
|
||||
/>
|
||||
),
|
||||
isSelected: panel === 'overview',
|
||||
'data-test-subj': `tab-overview`,
|
||||
href: getHref('integration_details_overview', {
|
||||
pkgkey: packageInfoKey,
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
// Don't show `custom` tab if a custom component is not registered
|
||||
if (panelId === 'custom' && !showCustomTab) {
|
||||
return false;
|
||||
}
|
||||
if (packageInstallStatus === InstallStatus.installed) {
|
||||
tabs.push({
|
||||
id: 'policies',
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetailsNav.packagePoliciesLinkText"
|
||||
defaultMessage="Policies"
|
||||
/>
|
||||
),
|
||||
isSelected: panel === 'policies',
|
||||
'data-test-subj': `tab-policies`,
|
||||
href: getHref('integration_details_policies', {
|
||||
pkgkey: packageInfoKey,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.map(([panelId, display]) => {
|
||||
return {
|
||||
id: panelId,
|
||||
name: display,
|
||||
isSelected: panelId === panel,
|
||||
'data-test-subj': `tab-${panelId}`,
|
||||
href: getHref('integration_details', {
|
||||
pkgkey: pkgKeyFromPackageInfo(packageInfo || {}),
|
||||
panel: panelId,
|
||||
}),
|
||||
};
|
||||
}) as unknown) as WithHeaderLayoutProps['tabs'];
|
||||
tabs.push({
|
||||
id: 'settings',
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetailsNav.settingsLinkText"
|
||||
defaultMessage="Settings"
|
||||
/>
|
||||
),
|
||||
isSelected: panel === 'settings',
|
||||
'data-test-subj': `tab-settings`,
|
||||
href: getHref('integration_details_settings', {
|
||||
pkgkey: packageInfoKey,
|
||||
}),
|
||||
});
|
||||
|
||||
if (showCustomTab) {
|
||||
tabs.push({
|
||||
id: 'custom',
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetailsNav.packageCustomLinkText"
|
||||
defaultMessage="Advanced"
|
||||
/>
|
||||
),
|
||||
isSelected: panel === 'custom',
|
||||
'data-test-subj': `tab-custom`,
|
||||
href: getHref('integration_details_custom', {
|
||||
pkgkey: packageInfoKey,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}, [getHref, packageInfo, panel, showCustomTab, packageInstallStatus]);
|
||||
|
||||
return (
|
||||
|
@ -344,7 +364,7 @@ export function Detail() {
|
|||
leftColumn={headerLeftContent}
|
||||
rightColumn={headerRightContent}
|
||||
rightColumnGrow={false}
|
||||
tabs={tabs}
|
||||
tabs={headerTabs}
|
||||
tabsClassName="fleet__epm__shiftNavTabs"
|
||||
>
|
||||
{packageInfo ? <Breadcrumbs packageTitle={packageInfo.title} /> : null}
|
||||
|
@ -361,7 +381,21 @@ export function Detail() {
|
|||
) : isLoading || !packageInfo ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<Content {...packageInfo} panel={panel} />
|
||||
<Switch>
|
||||
<Route path={PAGE_ROUTING_PATHS.integration_details_overview}>
|
||||
<OverviewPage packageInfo={packageInfo} />
|
||||
</Route>
|
||||
<Route path={PAGE_ROUTING_PATHS.integration_details_settings}>
|
||||
<SettingsPage packageInfo={packageInfo} />
|
||||
</Route>
|
||||
<Route path={PAGE_ROUTING_PATHS.integration_details_policies}>
|
||||
<PackagePoliciesPage name={packageInfo.name} version={packageInfo.version} />
|
||||
</Route>
|
||||
<Route path={PAGE_ROUTING_PATHS.integration_details_custom}>
|
||||
<CustomViewPage packageInfo={packageInfo} />
|
||||
</Route>
|
||||
<Redirect to={PAGE_ROUTING_PATHS.integration_details_overview} />
|
||||
</Switch>
|
||||
)}
|
||||
</WithHeaderLayout>
|
||||
);
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* 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 { EuiFlexItem } from '@elastic/eui';
|
||||
import React, { FunctionComponent, ReactNode } from 'react';
|
||||
import { FlexItemGrowSize } from '@elastic/eui/src/components/flex/flex_item';
|
||||
|
||||
interface ColumnProps {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
columnGrow?: FlexItemGrowSize;
|
||||
}
|
||||
|
||||
export const LeftColumn: FunctionComponent<ColumnProps> = ({
|
||||
columnGrow = 2,
|
||||
children,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<EuiFlexItem grow={columnGrow} {...rest}>
|
||||
{children}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
||||
|
||||
export const CenterColumn: FunctionComponent<ColumnProps> = ({
|
||||
columnGrow = 9,
|
||||
children,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<EuiFlexItem grow={columnGrow} {...rest}>
|
||||
{children}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
||||
|
||||
export const RightColumn: FunctionComponent<ColumnProps> = ({
|
||||
columnGrow = 3,
|
||||
children,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<EuiFlexItem grow={columnGrow} {...rest}>
|
||||
{children}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* 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 { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiTextColor,
|
||||
EuiDescriptionList,
|
||||
EuiNotificationBadge,
|
||||
} from '@elastic/eui';
|
||||
import { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list';
|
||||
import {
|
||||
PackageInfo,
|
||||
PackageSpecCategory,
|
||||
AssetTypeToParts,
|
||||
KibanaAssetType,
|
||||
entries,
|
||||
} from '../../../../../types';
|
||||
import { useGetCategories } from '../../../../../hooks';
|
||||
import { AssetTitleMap, DisplayedAssets, ServiceTitleMap } from '../../../constants';
|
||||
|
||||
interface Props {
|
||||
packageInfo: PackageInfo;
|
||||
}
|
||||
|
||||
export const Details: React.FC<Props> = memo(({ packageInfo }) => {
|
||||
const { data: categoriesData, isLoading: isLoadingCategories } = useGetCategories();
|
||||
const packageCategories: string[] = useMemo(() => {
|
||||
if (!isLoadingCategories && categoriesData && categoriesData.response) {
|
||||
return categoriesData.response
|
||||
.filter((category) => packageInfo.categories?.includes(category.id as PackageSpecCategory))
|
||||
.map((category) => category.title);
|
||||
}
|
||||
return [];
|
||||
}, [categoriesData, isLoadingCategories, packageInfo.categories]);
|
||||
|
||||
const listItems = useMemo(() => {
|
||||
// Base details: version and categories
|
||||
const items: EuiDescriptionListProps['listItems'] = [
|
||||
{
|
||||
title: (
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage id="xpack.fleet.epm.versionLabel" defaultMessage="Version" />
|
||||
</EuiTextColor>
|
||||
),
|
||||
description: packageInfo.version,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage id="xpack.fleet.epm.categoryLabel" defaultMessage="Category" />
|
||||
</EuiTextColor>
|
||||
),
|
||||
description: packageCategories.join(', '),
|
||||
},
|
||||
];
|
||||
|
||||
// Asset details and counts
|
||||
entries(packageInfo.assets).forEach(([service, typeToParts]) => {
|
||||
// Filter out assets we are not going to display
|
||||
// (currently we only display Kibana and Elasticsearch assets)
|
||||
const filteredTypes: AssetTypeToParts = entries(typeToParts).reduce(
|
||||
(acc: any, [asset, value]) => {
|
||||
if (DisplayedAssets[service].includes(asset)) acc[asset] = value;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
if (Object.entries(filteredTypes).length) {
|
||||
items.push({
|
||||
title: (
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.assetGroupTitle"
|
||||
defaultMessage="{assetType} assets"
|
||||
values={{
|
||||
assetType: ServiceTitleMap[service],
|
||||
}}
|
||||
/>
|
||||
</EuiTextColor>
|
||||
),
|
||||
description: (
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
{entries(filteredTypes).map(([_type, parts]) => {
|
||||
const type = _type as KibanaAssetType;
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>{AssetTitleMap[type]}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiNotificationBadge color="subdued">{parts.length}</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Feature (data stream type) details
|
||||
const dataStreamTypes = [
|
||||
...new Set(packageInfo.data_streams?.map((dataStream) => dataStream.type) || []),
|
||||
];
|
||||
if (dataStreamTypes.length) {
|
||||
items.push({
|
||||
title: (
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage id="xpack.fleet.epm.featuresLabel" defaultMessage="Features" />
|
||||
</EuiTextColor>
|
||||
),
|
||||
description: dataStreamTypes.join(', '),
|
||||
});
|
||||
}
|
||||
|
||||
// License details
|
||||
if (packageInfo.license) {
|
||||
items.push({
|
||||
title: (
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage id="xpack.fleet.epm.licenseLabel" defaultMessage="License" />
|
||||
</EuiTextColor>
|
||||
),
|
||||
description: packageInfo.license,
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}, [
|
||||
packageCategories,
|
||||
packageInfo.assets,
|
||||
packageInfo.data_streams,
|
||||
packageInfo.license,
|
||||
packageInfo.version,
|
||||
]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<h4>
|
||||
<FormattedMessage id="xpack.fleet.epm.detailsTitle" defaultMessage="Details" />
|
||||
</h4>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiDescriptionList type="column" compressed listItems={listItems} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
export { OverviewPage } from './overview';
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { PackageInfo } from '../../../../../types';
|
||||
import { Screenshots } from './screenshots';
|
||||
import { Readme } from './readme';
|
||||
import { Details } from './details';
|
||||
|
||||
interface Props {
|
||||
packageInfo: PackageInfo;
|
||||
}
|
||||
|
||||
const LeftColumn = styled(EuiFlexItem)`
|
||||
/* 🤢🤷 https://www.styled-components.com/docs/faqs#how-can-i-override-styles-with-higher-specificity */
|
||||
&&& {
|
||||
margin-top: 77px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const OverviewPage: React.FC<Props> = memo(({ packageInfo }: Props) => {
|
||||
return (
|
||||
<EuiFlexGroup alignItems="flexStart">
|
||||
<LeftColumn grow={2} />
|
||||
<EuiFlexItem grow={9}>
|
||||
{packageInfo.readme ? (
|
||||
<Readme
|
||||
readmePath={packageInfo.readme}
|
||||
packageName={packageInfo.name}
|
||||
version={packageInfo.version}
|
||||
/>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={3}>
|
||||
<EuiFlexGroup direction="column" gutterSize="l" alignItems="flexStart">
|
||||
{packageInfo.screenshots && packageInfo.screenshots.length ? (
|
||||
<EuiFlexItem>
|
||||
<Screenshots
|
||||
images={packageInfo.screenshots}
|
||||
packageName={packageInfo.name}
|
||||
version={packageInfo.version}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexItem>
|
||||
<Details packageInfo={packageInfo} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
|
@ -8,10 +8,9 @@
|
|||
import { EuiLoadingContent, EuiText } from '@elastic/eui';
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { useLinks } from '../../hooks';
|
||||
import { ContentCollapse } from './content_collapse';
|
||||
import { useLinks } from '../../../hooks';
|
||||
import { sendGetFileByPath } from '../../../../../hooks';
|
||||
import { markdownRenderers } from './markdown_renderers';
|
||||
import { sendGetFileByPath } from '../../../../hooks';
|
||||
|
||||
export function Readme({
|
||||
readmePath,
|
||||
|
@ -43,13 +42,11 @@ export function Readme({
|
|||
return (
|
||||
<Fragment>
|
||||
{markdown !== undefined ? (
|
||||
<ContentCollapse>
|
||||
<ReactMarkdown
|
||||
transformImageUri={handleImageUri}
|
||||
renderers={markdownRenderers}
|
||||
source={markdown}
|
||||
/>
|
||||
</ContentCollapse>
|
||||
<ReactMarkdown
|
||||
transformImageUri={handleImageUri}
|
||||
renderers={markdownRenderers}
|
||||
source={markdown}
|
||||
/>
|
||||
) : (
|
||||
<EuiText>
|
||||
{/* simulates a long page of text loading */}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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, { useState, useMemo, memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiImage, EuiText, EuiPagination } from '@elastic/eui';
|
||||
import { ScreenshotItem } from '../../../../../types';
|
||||
import { useLinks } from '../../../hooks';
|
||||
|
||||
interface ScreenshotProps {
|
||||
images: ScreenshotItem[];
|
||||
packageName: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export const Screenshots: React.FC<ScreenshotProps> = memo(({ images, packageName, version }) => {
|
||||
const { toPackageImage } = useLinks();
|
||||
const [currentImageIndex, setCurrentImageIndex] = useState<number>(0);
|
||||
const maxImageIndex = useMemo(() => images.length - 1, [images.length]);
|
||||
const currentImageUrl = useMemo(
|
||||
() => toPackageImage(images[currentImageIndex], packageName, version),
|
||||
[currentImageIndex, images, packageName, toPackageImage, version]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
{/* Title with carousel navigation */}
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gutterSize="xs"
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.screenshotsTitle"
|
||||
defaultMessage="Screenshots"
|
||||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPagination
|
||||
aria-label={i18n.translate('xpack.fleet.epm.screenshotPaginationAriaLabel', {
|
||||
defaultMessage: '{packageName} screenshot pagination',
|
||||
values: {
|
||||
packageName,
|
||||
},
|
||||
})}
|
||||
pageCount={maxImageIndex + 1}
|
||||
activePage={currentImageIndex}
|
||||
onPageClick={(activePage) => setCurrentImageIndex(activePage)}
|
||||
compressed
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
{/* Current screenshot */}
|
||||
<EuiFlexItem>
|
||||
{currentImageUrl ? (
|
||||
<EuiImage
|
||||
allowFullScreen
|
||||
hasShadow
|
||||
alt={
|
||||
images[currentImageIndex].title ||
|
||||
i18n.translate('xpack.fleet.epm.screenshotAltText', {
|
||||
defaultMessage: '{packageName} screenshot #{imageNumber}',
|
||||
values: {
|
||||
packageName,
|
||||
imageNumber: currentImageIndex + 1,
|
||||
},
|
||||
})
|
||||
}
|
||||
title={images[currentImageIndex].title}
|
||||
url={currentImageUrl}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.screenshotErrorText"
|
||||
defaultMessage="Unable to load this screenshot"
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* 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 { EuiSpacer } from '@elastic/eui';
|
||||
import React, { Fragment } from 'react';
|
||||
import { PackageInfo } from '../../../../types';
|
||||
import { Readme } from './readme';
|
||||
import { Screenshots } from './screenshots';
|
||||
|
||||
export function OverviewPanel(props: PackageInfo) {
|
||||
const { screenshots, readme, name, version } = props;
|
||||
return (
|
||||
<Fragment>
|
||||
{readme && <Readme readmePath={readme} packageName={name} version={version} />}
|
||||
<EuiSpacer size="xl" />
|
||||
{screenshots && <Screenshots images={screenshots} packageName={name} version={version} />}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
export { PackagePoliciesPage } from './package_policies';
|
|
@ -12,21 +12,22 @@ import {
|
|||
EuiBasicTable,
|
||||
EuiLink,
|
||||
EuiTableFieldDataColumnType,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedRelative, FormattedMessage } from '@kbn/i18n/react';
|
||||
import { useGetPackageInstallStatus } from '../../hooks';
|
||||
import { InstallStatus } from '../../../../types';
|
||||
import { useLink } from '../../../../hooks';
|
||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../common/constants';
|
||||
import { useUrlPagination } from '../../../../hooks';
|
||||
import { InstallStatus } from '../../../../../types';
|
||||
import { useLink, useUrlPagination } from '../../../../../hooks';
|
||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants';
|
||||
import { LinkAndRevision, LinkAndRevisionProps } from '../../../../../components';
|
||||
import { LinkedAgentCount } from '../../../../../components/linked_agent_count';
|
||||
import { useGetPackageInstallStatus } from '../../../hooks';
|
||||
import {
|
||||
PackagePolicyAndAgentPolicy,
|
||||
usePackagePoliciesWithAgentPolicy,
|
||||
} from './use_package_policies_with_agent_policy';
|
||||
import { LinkAndRevision, LinkAndRevisionProps } from '../../../../components';
|
||||
import { Persona } from './persona';
|
||||
import { LinkedAgentCount } from '../../../../components/linked_agent_count';
|
||||
|
||||
const IntegrationDetailsLink = memo<{
|
||||
packagePolicy: PackagePolicyAndAgentPolicy['packagePolicy'];
|
||||
|
@ -52,6 +53,7 @@ const AgentPolicyDetailLink = memo<{
|
|||
children: ReactNode;
|
||||
}>(({ agentPolicyId, revision, children }) => {
|
||||
const { getHref } = useLink();
|
||||
|
||||
return (
|
||||
<LinkAndRevision
|
||||
className="eui-textTruncate"
|
||||
|
@ -69,7 +71,7 @@ interface PackagePoliciesPanelProps {
|
|||
name: string;
|
||||
version: string;
|
||||
}
|
||||
export const PackagePoliciesPanel = ({ name, version }: PackagePoliciesPanelProps) => {
|
||||
export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps) => {
|
||||
const { getPath } = useLink();
|
||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||
const packageInstallStatus = getPackageInstallStatus(name);
|
||||
|
@ -197,18 +199,25 @@ export const PackagePoliciesPanel = ({ name, version }: PackagePoliciesPanelProp
|
|||
// if they arrive at this page and the package is not installed, send them to overview
|
||||
// this happens if they arrive with a direct url or they uninstall while on this tab
|
||||
if (packageInstallStatus.status !== InstallStatus.installed) {
|
||||
return <Redirect to={getPath('integration_details', { pkgkey: `${name}-${version}` })} />;
|
||||
return (
|
||||
<Redirect to={getPath('integration_details_overview', { pkgkey: `${name}-${version}` })} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiBasicTable
|
||||
items={data?.items || []}
|
||||
columns={columns}
|
||||
loading={isLoading}
|
||||
data-test-subj="integrationPolicyTable"
|
||||
pagination={tablePagination}
|
||||
onChange={handleTableOnChange}
|
||||
noItemsMessage={noItemsMessage}
|
||||
/>
|
||||
<EuiFlexGroup alignItems="flexStart">
|
||||
<EuiFlexItem grow={1} />
|
||||
<EuiFlexItem grow={6}>
|
||||
<EuiBasicTable
|
||||
items={data?.items || []}
|
||||
columns={columns}
|
||||
loading={isLoading}
|
||||
data-test-subj="integrationPolicyTable"
|
||||
pagination={tablePagination}
|
||||
onChange={handleTableOnChange}
|
||||
noItemsMessage={noItemsMessage}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -4,9 +4,8 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiAvatar, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import React, { CSSProperties, memo, useCallback } from 'react';
|
||||
import { EuiAvatar, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import { EuiAvatarProps } from '@elastic/eui/src/components/avatar/avatar';
|
||||
|
||||
const MIN_WIDTH: CSSProperties = { minWidth: 0 };
|
|
@ -6,19 +6,19 @@
|
|||
*/
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { PackagePolicy } from '../../../../../../../common/types/models';
|
||||
import {
|
||||
PackagePolicy,
|
||||
GetAgentPoliciesResponse,
|
||||
GetAgentPoliciesResponseItem,
|
||||
} from '../../../../../../../common/types/rest_spec';
|
||||
import { useGetPackagePolicies } from '../../../../hooks/use_request';
|
||||
GetPackagePoliciesResponse,
|
||||
} from '../../../../../types';
|
||||
import { agentPolicyRouteService } from '../../../../../services';
|
||||
import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants';
|
||||
import { useGetPackagePolicies } from '../../../../../hooks';
|
||||
import {
|
||||
SendConditionalRequestConfig,
|
||||
useConditionalRequest,
|
||||
} from '../../../../hooks/use_request/use_request';
|
||||
import { agentPolicyRouteService } from '../../../../../../../common/services';
|
||||
import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../common/constants';
|
||||
import { GetPackagePoliciesResponse } from '../../../../../../../common/types/rest_spec';
|
||||
} from '../../../../../hooks/use_request/use_request';
|
||||
|
||||
export interface PackagePolicyEnriched extends PackagePolicy {
|
||||
_agentPolicy: GetAgentPoliciesResponseItem | undefined;
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* 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, { Fragment } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiImage, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { ScreenshotItem } from '../../../../types';
|
||||
import { useLinks } from '../../hooks';
|
||||
|
||||
interface ScreenshotProps {
|
||||
images: ScreenshotItem[];
|
||||
packageName: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
const getHorizontalPadding = (styledProps: any): number =>
|
||||
parseInt(styledProps.theme.eui.paddingSizes.xl, 10) * 2;
|
||||
const getVerticalPadding = (styledProps: any): number =>
|
||||
parseInt(styledProps.theme.eui.paddingSizes.xl, 10) * 1.75;
|
||||
const getPadding = (styledProps: any) =>
|
||||
styledProps.hascaption
|
||||
? `${styledProps.theme.eui.paddingSizes.xl} ${getHorizontalPadding(
|
||||
styledProps
|
||||
)}px ${getVerticalPadding(styledProps)}px`
|
||||
: `${getHorizontalPadding(styledProps)}px ${getVerticalPadding(styledProps)}px`;
|
||||
const ScreenshotsContainer = styled(EuiFlexGroup)`
|
||||
background: linear-gradient(360deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0) 100%),
|
||||
${(styledProps) => styledProps.theme.eui.euiColorPrimary};
|
||||
padding: ${(styledProps) => getPadding(styledProps)};
|
||||
flex: 0 0 auto;
|
||||
border-radius: ${(styledProps) => styledProps.theme.eui.euiBorderRadius};
|
||||
`;
|
||||
|
||||
// fixes ie11 problems with nested flex items
|
||||
const NestedEuiFlexItem = styled(EuiFlexItem)`
|
||||
flex: 0 0 auto !important;
|
||||
`;
|
||||
|
||||
export function Screenshots(props: ScreenshotProps) {
|
||||
const { toPackageImage } = useLinks();
|
||||
const { images, packageName, version } = props;
|
||||
|
||||
// for now, just get first image
|
||||
const image = images[0];
|
||||
const hasCaption = image.title ? true : false;
|
||||
const screenshotUrl = toPackageImage(image, packageName, version);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage id="xpack.fleet.epm.screenshotsTitle" defaultMessage="Screenshots" />
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<ScreenshotsContainer
|
||||
gutterSize="none"
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
{...(hasCaption ? { hascaption: 'true' } : {})}
|
||||
>
|
||||
{hasCaption && (
|
||||
<NestedEuiFlexItem>
|
||||
<EuiText color="ghost" aria-label="screenshot image caption">
|
||||
{image.title}
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
</NestedEuiFlexItem>
|
||||
)}
|
||||
{screenshotUrl && (
|
||||
<NestedEuiFlexItem>
|
||||
{/* By default EuiImage sets width to 100% and Figure to 22.5rem for size=l images,
|
||||
set image to same width. Will need to update if size changes.
|
||||
*/}
|
||||
<EuiImage
|
||||
url={screenshotUrl}
|
||||
alt="screenshot image preview"
|
||||
size="l"
|
||||
allowFullScreen
|
||||
style={{ width: '22.5rem', maxWidth: '100%' }}
|
||||
/>
|
||||
</NestedEuiFlexItem>
|
||||
)}
|
||||
</ScreenshotsContainer>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
export { SettingsPage } from './settings';
|
|
@ -8,9 +8,9 @@
|
|||
import { EuiButton } from '@elastic/eui';
|
||||
import React, { Fragment, useCallback, useMemo, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { PackageInfo, InstallStatus } from '../../../../types';
|
||||
import { useCapabilities } from '../../../../hooks';
|
||||
import { useUninstallPackage, useGetPackageInstallStatus, useInstallPackage } from '../../hooks';
|
||||
import { PackageInfo, InstallStatus } from '../../../../../types';
|
||||
import { useCapabilities } from '../../../../../hooks';
|
||||
import { useUninstallPackage, useGetPackageInstallStatus, useInstallPackage } from '../../../hooks';
|
||||
import { ConfirmPackageUninstall } from './confirm_package_uninstall';
|
||||
import { ConfirmPackageInstall } from './confirm_package_install';
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* 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 } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui';
|
||||
import { InstallStatus, PackageInfo } from '../../../../../types';
|
||||
import { useGetPackagePolicies } from '../../../../../hooks';
|
||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants';
|
||||
import { useGetPackageInstallStatus } from '../../../hooks';
|
||||
import { UpdateIcon } from '../components';
|
||||
import { InstallationButton } from './installation_button';
|
||||
|
||||
const SettingsTitleCell = styled.td`
|
||||
padding-right: ${(props) => props.theme.eui.spacerSizes.xl};
|
||||
padding-bottom: ${(props) => props.theme.eui.spacerSizes.m};
|
||||
`;
|
||||
|
||||
const UpdatesAvailableMsgContainer = styled.span`
|
||||
padding-left: ${(props) => props.theme.eui.spacerSizes.s};
|
||||
`;
|
||||
|
||||
const NoteLabel = () => (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageUninstallNoteDescription.packageUninstallNoteLabel"
|
||||
defaultMessage="Note:"
|
||||
/>
|
||||
);
|
||||
const UpdatesAvailableMsg = () => (
|
||||
<UpdatesAvailableMsgContainer>
|
||||
<UpdateIcon size="l" />
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.versionInfo.updatesAvailable"
|
||||
defaultMessage="Updates are available"
|
||||
/>
|
||||
</UpdatesAvailableMsgContainer>
|
||||
);
|
||||
|
||||
interface Props {
|
||||
packageInfo: PackageInfo;
|
||||
}
|
||||
|
||||
export const SettingsPage: React.FC<Props> = memo(({ packageInfo }: Props) => {
|
||||
const { name, title, removable, latestVersion, version } = packageInfo;
|
||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||
const { data: packagePoliciesData } = useGetPackagePolicies({
|
||||
perPage: 0,
|
||||
page: 1,
|
||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${name}`,
|
||||
});
|
||||
const { status: installationStatus, version: installedVersion } = getPackageInstallStatus(name);
|
||||
const packageHasUsages = !!packagePoliciesData?.total;
|
||||
const updateAvailable = installedVersion && installedVersion < latestVersion ? true : false;
|
||||
const isViewingOldPackage = version < latestVersion;
|
||||
// hide install/remove options if the user has version of the package is installed
|
||||
// and this package is out of date or if they do have a version installed but it's not this one
|
||||
const hideInstallOptions =
|
||||
(installationStatus === InstallStatus.notInstalled && isViewingOldPackage) ||
|
||||
(installationStatus === InstallStatus.installed && installedVersion !== version);
|
||||
|
||||
const isUpdating = installationStatus === InstallStatus.installing && installedVersion;
|
||||
return (
|
||||
<EuiFlexGroup alignItems="flexStart">
|
||||
<EuiFlexItem grow={1} />
|
||||
<EuiFlexItem grow={6}>
|
||||
<EuiText>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageSettingsTitle"
|
||||
defaultMessage="Settings"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
{installedVersion !== null && (
|
||||
<div>
|
||||
<EuiTitle>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageVersionTitle"
|
||||
defaultMessage="{title} version"
|
||||
values={{
|
||||
title,
|
||||
}}
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<SettingsTitleCell>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.versionInfo.installedVersion"
|
||||
defaultMessage="Installed version"
|
||||
/>
|
||||
</SettingsTitleCell>
|
||||
<td>
|
||||
<EuiTitle size="xs">
|
||||
<span>{installedVersion}</span>
|
||||
</EuiTitle>
|
||||
{updateAvailable && <UpdatesAvailableMsg />}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<SettingsTitleCell>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.versionInfo.latestVersion"
|
||||
defaultMessage="Latest version"
|
||||
/>
|
||||
</SettingsTitleCell>
|
||||
<td>
|
||||
<EuiTitle size="xs">
|
||||
<span>{latestVersion}</span>
|
||||
</EuiTitle>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{updateAvailable && (
|
||||
<p>
|
||||
<InstallationButton
|
||||
{...packageInfo}
|
||||
version={latestVersion}
|
||||
disabled={false}
|
||||
isUpdate={true}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!hideInstallOptions && !isUpdating && (
|
||||
<div>
|
||||
<EuiSpacer size="s" />
|
||||
{installationStatus === InstallStatus.notInstalled ||
|
||||
installationStatus === InstallStatus.installing ? (
|
||||
<div>
|
||||
<EuiTitle>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageInstallTitle"
|
||||
defaultMessage="Install {title}"
|
||||
values={{
|
||||
title,
|
||||
}}
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageInstallDescription"
|
||||
defaultMessage="Install this integration to setup Kibana and Elasticsearch assets designed for {title} data."
|
||||
values={{
|
||||
title,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<EuiTitle>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageUninstallTitle"
|
||||
defaultMessage="Uninstall {title}"
|
||||
values={{
|
||||
title,
|
||||
}}
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageUninstallDescription"
|
||||
defaultMessage="Remove Kibana and Elasticsearch assets that were installed by this integration."
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<p>
|
||||
<InstallationButton
|
||||
{...packageInfo}
|
||||
disabled={
|
||||
!packagePoliciesData || removable === false ? true : packageHasUsages
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{packageHasUsages && removable === true && (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageUninstallNoteDescription.packageUninstallNoteDetail"
|
||||
defaultMessage="{strongNote} {title} cannot be uninstalled because there are active agents that use this integration. To uninstall, remove all {title} integrations from your agent policies."
|
||||
values={{
|
||||
title,
|
||||
strongNote: (
|
||||
<strong>
|
||||
<NoteLabel />
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
{removable === false && (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageUninstallNoteDescription.packageUninstallUninstallableNoteDetail"
|
||||
defaultMessage="{strongNote} The {title} integration is installed by default and cannot be removed."
|
||||
values={{
|
||||
title,
|
||||
strongNote: (
|
||||
<strong>
|
||||
<NoteLabel />
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
|
@ -1,230 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { InstallStatus, PackageInfo } from '../../../../types';
|
||||
import { useGetPackagePolicies } from '../../../../hooks';
|
||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../constants';
|
||||
import { useGetPackageInstallStatus } from '../../hooks';
|
||||
import { InstallationButton } from './installation_button';
|
||||
import { UpdateIcon } from '../../components/icons';
|
||||
|
||||
const SettingsTitleCell = styled.td`
|
||||
padding-right: ${(props) => props.theme.eui.spacerSizes.xl};
|
||||
padding-bottom: ${(props) => props.theme.eui.spacerSizes.m};
|
||||
`;
|
||||
|
||||
const UpdatesAvailableMsgContainer = styled.span`
|
||||
padding-left: ${(props) => props.theme.eui.spacerSizes.s};
|
||||
`;
|
||||
|
||||
const NoteLabel = () => (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageUninstallNoteDescription.packageUninstallNoteLabel"
|
||||
defaultMessage="Note:"
|
||||
/>
|
||||
);
|
||||
const UpdatesAvailableMsg = () => (
|
||||
<UpdatesAvailableMsgContainer>
|
||||
<UpdateIcon size="l" />
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.versionInfo.updatesAvailable"
|
||||
defaultMessage="Updates are available"
|
||||
/>
|
||||
</UpdatesAvailableMsgContainer>
|
||||
);
|
||||
|
||||
export const SettingsPanel = (
|
||||
props: Pick<PackageInfo, 'assets' | 'name' | 'title' | 'version' | 'removable' | 'latestVersion'>
|
||||
) => {
|
||||
const { name, title, removable, latestVersion, version } = props;
|
||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||
const { data: packagePoliciesData } = useGetPackagePolicies({
|
||||
perPage: 0,
|
||||
page: 1,
|
||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${props.name}`,
|
||||
});
|
||||
const { status: installationStatus, version: installedVersion } = getPackageInstallStatus(name);
|
||||
const packageHasUsages = !!packagePoliciesData?.total;
|
||||
const updateAvailable = installedVersion && installedVersion < latestVersion ? true : false;
|
||||
const isViewingOldPackage = version < latestVersion;
|
||||
// hide install/remove options if the user has version of the package is installed
|
||||
// and this package is out of date or if they do have a version installed but it's not this one
|
||||
const hideInstallOptions =
|
||||
(installationStatus === InstallStatus.notInstalled && isViewingOldPackage) ||
|
||||
(installationStatus === InstallStatus.installed && installedVersion !== version);
|
||||
|
||||
const isUpdating = installationStatus === InstallStatus.installing && installedVersion;
|
||||
return (
|
||||
<EuiText>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageSettingsTitle"
|
||||
defaultMessage="Settings"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
{installedVersion !== null && (
|
||||
<div>
|
||||
<EuiTitle>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageVersionTitle"
|
||||
defaultMessage="{title} version"
|
||||
values={{
|
||||
title,
|
||||
}}
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<SettingsTitleCell>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.versionInfo.installedVersion"
|
||||
defaultMessage="Installed version"
|
||||
/>
|
||||
</SettingsTitleCell>
|
||||
<td>
|
||||
<EuiTitle size="xs">
|
||||
<span>{installedVersion}</span>
|
||||
</EuiTitle>
|
||||
{updateAvailable && <UpdatesAvailableMsg />}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<SettingsTitleCell>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.versionInfo.latestVersion"
|
||||
defaultMessage="Latest version"
|
||||
/>
|
||||
</SettingsTitleCell>
|
||||
<td>
|
||||
<EuiTitle size="xs">
|
||||
<span>{latestVersion}</span>
|
||||
</EuiTitle>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{updateAvailable && (
|
||||
<p>
|
||||
<InstallationButton
|
||||
{...props}
|
||||
version={latestVersion}
|
||||
disabled={false}
|
||||
isUpdate={true}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!hideInstallOptions && !isUpdating && (
|
||||
<div>
|
||||
<EuiSpacer size="s" />
|
||||
{installationStatus === InstallStatus.notInstalled ||
|
||||
installationStatus === InstallStatus.installing ? (
|
||||
<div>
|
||||
<EuiTitle>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageInstallTitle"
|
||||
defaultMessage="Install {title}"
|
||||
values={{
|
||||
title,
|
||||
}}
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageInstallDescription"
|
||||
defaultMessage="Install this integration to setup Kibana and Elasticsearch assets designed for {title} data."
|
||||
values={{
|
||||
title,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<EuiTitle>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageUninstallTitle"
|
||||
defaultMessage="Uninstall {title}"
|
||||
values={{
|
||||
title,
|
||||
}}
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageUninstallDescription"
|
||||
defaultMessage="Remove Kibana and Elasticsearch assets that were installed by this integration."
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<p>
|
||||
<InstallationButton
|
||||
{...props}
|
||||
disabled={!packagePoliciesData || removable === false ? true : packageHasUsages}
|
||||
/>
|
||||
</p>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{packageHasUsages && removable === true && (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageUninstallNoteDescription.packageUninstallNoteDetail"
|
||||
defaultMessage="{strongNote} {title} cannot be uninstalled because there are active agents that use this integration. To uninstall, remove all {title} integrations from your agent policies."
|
||||
values={{
|
||||
title,
|
||||
strongNote: (
|
||||
<strong>
|
||||
<NoteLabel />
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
{removable === false && (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.integrations.settings.packageUninstallNoteDescription.packageUninstallUninstallableNoteDetail"
|
||||
defaultMessage="{strongNote} The {title} integration is installed by default and cannot be removed."
|
||||
values={{
|
||||
title,
|
||||
strongNote: (
|
||||
<strong>
|
||||
<NoteLabel />
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</EuiText>
|
||||
);
|
||||
};
|
|
@ -49,6 +49,7 @@ export {
|
|||
CreatePackagePolicyResponse,
|
||||
UpdatePackagePolicyRequest,
|
||||
UpdatePackagePolicyResponse,
|
||||
GetPackagePoliciesResponse,
|
||||
// API schemas - Data streams
|
||||
GetDataStreamsResponse,
|
||||
// API schemas - Agents
|
||||
|
@ -122,6 +123,7 @@ export {
|
|||
InstallationStatus,
|
||||
Installable,
|
||||
RegistryRelease,
|
||||
PackageSpecCategory,
|
||||
} from '../../../../common';
|
||||
|
||||
export * from './intra_app_route_state';
|
||||
|
|
|
@ -32,10 +32,7 @@ export const FleetTrustedAppsCard = memo<PackageCustomExtensionComponentProps>((
|
|||
const trustedAppsListUrlPath = getTrustedAppsListPath();
|
||||
|
||||
const trustedAppRouteState = useMemo<TrustedAppsListPageRouteState>(() => {
|
||||
const fleetPackageCustomUrlPath = `#${pagePathGetters.integration_details({
|
||||
pkgkey,
|
||||
panel: 'custom',
|
||||
})}`;
|
||||
const fleetPackageCustomUrlPath = `#${pagePathGetters.integration_details_custom({ pkgkey })}`;
|
||||
return {
|
||||
backButtonLabel: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel',
|
||||
|
|
|
@ -18,7 +18,7 @@ export function FleetIntegrations({ getService, getPageObjects }: FtrProviderCon
|
|||
return {
|
||||
async navigateToIntegrationDetails(pkgkey: string) {
|
||||
await pageObjects.common.navigateToApp(PLUGIN_ID, {
|
||||
hash: pagePathGetters.integration_details({ pkgkey }),
|
||||
hash: pagePathGetters.integration_details_overview({ pkgkey }),
|
||||
});
|
||||
},
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue