diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 0ef9f8b7ace3..f19684b0445e 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -48,7 +48,12 @@ export type EpmPackageInstallStatus = 'installed' | 'installing'; export type DetailViewPanelName = 'overview' | 'policies' | 'settings' | 'custom'; export type ServiceName = 'kibana' | 'elasticsearch'; export type AgentAssetType = typeof agentAssetTypes; -export type AssetType = KibanaAssetType | ElasticsearchAssetType | ValueOf; +export type DocAssetType = 'doc' | 'notice'; +export type AssetType = + | KibanaAssetType + | ElasticsearchAssetType + | ValueOf + | DocAssetType; /* Enum mapping of a saved object asset type to how it would appear in a package file path (snake cased) @@ -344,6 +349,7 @@ export interface EpmPackageAdditions { latestVersion: string; assets: AssetsGroupedByServiceByType; removable?: boolean; + notice?: string; } type Merge = Omit> & diff --git a/x-pack/plugins/fleet/public/applications/integrations/constants.tsx b/x-pack/plugins/fleet/public/applications/integrations/constants.tsx index 403a47f4b94b..08197e18fec0 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/constants.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/constants.tsx @@ -7,7 +7,7 @@ import type { IconType } from '@elastic/eui'; -import type { AssetType, ServiceName } from '../../types'; +import type { ServiceName } from '../../types'; import { ElasticsearchAssetType, KibanaAssetType } from '../../types'; export * from '../../constants'; @@ -20,8 +20,9 @@ export const DisplayedAssets: ServiceNameToAssetTypes = { kibana: Object.values(KibanaAssetType), elasticsearch: Object.values(ElasticsearchAssetType), }; +export type DisplayedAssetType = KibanaAssetType | ElasticsearchAssetType; -export const AssetTitleMap: Record = { +export const AssetTitleMap: Record = { dashboard: 'Dashboard', ilm_policy: 'ILM Policy', ingest_pipeline: 'Ingest Pipeline', @@ -31,7 +32,6 @@ export const AssetTitleMap: Record = { component_template: 'Component Template', search: 'Saved Search', visualization: 'Visualization', - input: 'Agent input', map: 'Map', data_stream_ilm_policy: 'Data Stream ILM Policy', lens: 'Lens', diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx index 6ddff968bd3f..41db09b0538b 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx @@ -7,7 +7,7 @@ import type { IconType } from '@elastic/eui'; -import type { AssetType, ServiceName } from '../../types'; +import type { ServiceName } from '../../types'; import { ElasticsearchAssetType, KibanaAssetType } from '../../types'; // only allow Kibana assets for the kibana key, ES asssets for elasticsearch, etc @@ -19,7 +19,9 @@ export const DisplayedAssets: ServiceNameToAssetTypes = { elasticsearch: Object.values(ElasticsearchAssetType), }; -export const AssetTitleMap: Record = { +export type DisplayedAssetType = ElasticsearchAssetType | KibanaAssetType; + +export const AssetTitleMap: Record = { dashboard: 'Dashboard', ilm_policy: 'ILM Policy', ingest_pipeline: 'Ingest Pipeline', @@ -29,7 +31,6 @@ export const AssetTitleMap: Record = { component_template: 'Component Template', search: 'Saved Search', visualization: 'Visualization', - input: 'Agent input', map: 'Map', data_stream_ilm_policy: 'Data Stream ILM Policy', lens: 'Lens', diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx index 487df1798034..0a601d2128bb 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { memo, useMemo } from 'react'; +import React, { memo, useCallback, useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, @@ -13,6 +13,8 @@ import { EuiTextColor, EuiDescriptionList, EuiNotificationBadge, + EuiLink, + EuiPortal, } from '@elastic/eui'; import type { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list'; @@ -26,6 +28,8 @@ import { entries } from '../../../../../types'; import { useGetCategories } from '../../../../../hooks'; import { AssetTitleMap, DisplayedAssets, ServiceTitleMap } from '../../../constants'; +import { NoticeModal } from './notice_modal'; + interface Props { packageInfo: PackageInfo; } @@ -41,6 +45,11 @@ export const Details: React.FC = memo(({ packageInfo }) => { return []; }, [categoriesData, isLoadingCategories, packageInfo.categories]); + const [isNoticeModalOpen, setIsNoticeModalOpen] = useState(false); + const toggleNoticeModal = useCallback(() => { + setIsNoticeModalOpen(!isNoticeModalOpen); + }, [isNoticeModalOpen]); + const listItems = useMemo(() => { // Base details: version and categories const items: EuiDescriptionListProps['listItems'] = [ @@ -123,14 +132,23 @@ export const Details: React.FC = memo(({ packageInfo }) => { } // License details - if (packageInfo.license) { + if (packageInfo.license || packageInfo.notice) { items.push({ title: ( ), - description: packageInfo.license, + description: ( + <> +

{packageInfo.license}

+ {packageInfo.notice && ( +

+ NOTICE.txt +

+ )} + + ), }); } @@ -140,21 +158,30 @@ export const Details: React.FC = memo(({ packageInfo }) => { packageInfo.assets, packageInfo.data_streams, packageInfo.license, + packageInfo.notice, packageInfo.version, + toggleNoticeModal, ]); return ( - - - -

- -

-
-
- - - -
+ <> + + {isNoticeModalOpen && packageInfo.notice && ( + + )} + + + + +

+ +

+
+
+ + + +
+ ); }); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx new file mode 100644 index 000000000000..239bd133d3c9 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx @@ -0,0 +1,79 @@ +/* + * 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, { useEffect, useState } from 'react'; +import { + EuiCodeBlock, + EuiLoadingContent, + EuiModal, + EuiModalBody, + EuiModalHeader, + EuiModalFooter, + EuiModalHeaderTitle, + EuiButton, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { sendGetFileByPath, useStartServices } from '../../../../../hooks'; + +interface Props { + noticePath: string; + onClose: () => void; +} + +export const NoticeModal: React.FunctionComponent = ({ noticePath, onClose }) => { + const { notifications } = useStartServices(); + const [notice, setNotice] = useState(undefined); + + useEffect(() => { + async function fetchData() { + try { + const { data } = await sendGetFileByPath(noticePath); + setNotice(data || ''); + } catch (err) { + notifications.toasts.addError(err, { + title: i18n.translate('xpack.fleet.epm.errorLoadingNotice', { + defaultMessage: 'Error loading NOTICE.txt', + }), + }); + } + } + fetchData(); + }, [noticePath, notifications]); + return ( + + + +

NOTICE.txt

+
+
+ + + {notice ? ( + notice + ) : ( + // Simulate a long notice while loading + <> +

+ +

+

+ +

+ + )} +
+
+ + + + + +
+ ); +}; diff --git a/x-pack/plugins/fleet/server/services/epm/archive/index.ts b/x-pack/plugins/fleet/server/services/epm/archive/index.ts index 809684df0592..b08ec815a394 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/index.ts @@ -114,6 +114,13 @@ export function getPathParts(path: string): AssetParts { [pkgkey, service, type, file] = path.replace(`data_stream/${dataset}/`, '').split('/'); } + // To support the NOTICE asset at the root level + if (service === 'NOTICE.txt') { + file = service; + type = 'notice'; + service = ''; + } + // This is to cover for the fields.yml files inside the "fields" directory if (file === undefined) { file = type; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index e4e4f9c55fd2..404431816d10 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -126,6 +126,7 @@ export async function getPackageInfo(options: { title: packageInfo.title || nameAsTitle(packageInfo.name), assets: Registry.groupPathsByService(paths || []), removable: !isRequiredPackage(pkgName), + notice: Registry.getNoticePath(paths || []), }; const updated = { ...packageInfo, ...additions }; diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.ts index 5ee7e198555c..011a0e74e8c1 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/index.ts @@ -255,3 +255,15 @@ export function groupPathsByService(paths: string[]): AssetsGroupedByServiceByTy elasticsearch: assets.elasticsearch, }; } + +export function getNoticePath(paths: string[]): string | undefined { + for (const path of paths) { + const parts = getPathParts(path.replace(/^\/package\//, '')); + if (parts.type === 'notice') { + const { pkgName, pkgVersion } = splitPkgKey(parts.pkgkey); + return `/package/${pkgName}/${pkgVersion}/${parts.file}`; + } + } + + return undefined; +}