Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion x-pack/plugins/fleet/common/types/models/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<AgentAssetType>;
export type DocAssetType = 'doc' | 'notice';
export type AssetType =
| KibanaAssetType
| ElasticsearchAssetType
| ValueOf<AgentAssetType>
| DocAssetType;

/*
Enum mapping of a saved object asset type to how it would appear in a package file path (snake cased)
Expand Down Expand Up @@ -344,6 +349,7 @@ export interface EpmPackageAdditions {
latestVersion: string;
assets: AssetsGroupedByServiceByType;
removable?: boolean;
notice?: string;
}

type Merge<FirstType, SecondType> = Omit<FirstType, Extract<keyof FirstType, keyof SecondType>> &
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<AssetType, string> = {
export const AssetTitleMap: Record<DisplayedAssetType, string> = {
dashboard: 'Dashboard',
ilm_policy: 'ILM Policy',
ingest_pipeline: 'Ingest Pipeline',
Expand All @@ -31,7 +32,6 @@ export const AssetTitleMap: Record<AssetType, string> = {
component_template: 'Component Template',
search: 'Saved Search',
visualization: 'Visualization',
input: 'Agent input',
map: 'Map',
data_stream_ilm_policy: 'Data Stream ILM Policy',
lens: 'Lens',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,7 +19,9 @@ export const DisplayedAssets: ServiceNameToAssetTypes = {
elasticsearch: Object.values(ElasticsearchAssetType),
};

export const AssetTitleMap: Record<AssetType, string> = {
export type DisplayedAssetType = ElasticsearchAssetType | KibanaAssetType;

export const AssetTitleMap: Record<DisplayedAssetType, string> = {
dashboard: 'Dashboard',
ilm_policy: 'ILM Policy',
ingest_pipeline: 'Ingest Pipeline',
Expand All @@ -29,7 +31,6 @@ export const AssetTitleMap: Record<AssetType, string> = {
component_template: 'Component Template',
search: 'Saved Search',
visualization: 'Visualization',
input: 'Agent input',
map: 'Map',
data_stream_ilm_policy: 'Data Stream ILM Policy',
lens: 'Lens',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';

Expand All @@ -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;
}
Expand All @@ -41,6 +45,11 @@ export const Details: React.FC<Props> = 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'] = [
Expand Down Expand Up @@ -123,14 +132,23 @@ export const Details: React.FC<Props> = memo(({ packageInfo }) => {
}

// License details
if (packageInfo.license) {
if (packageInfo.license || packageInfo.notice) {
items.push({
title: (
<EuiTextColor color="subdued">
<FormattedMessage id="xpack.fleet.epm.licenseLabel" defaultMessage="License" />
</EuiTextColor>
),
description: packageInfo.license,
description: (
<>
<p>{packageInfo.license}</p>
{packageInfo.notice && (
<p>
<EuiLink onClick={toggleNoticeModal}>NOTICE.txt</EuiLink>
</p>
)}
</>
),
});
}

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

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>
<>
<EuiPortal>
{isNoticeModalOpen && packageInfo.notice && (
<NoticeModal noticePath={packageInfo.notice} onClose={toggleNoticeModal} />
)}
</EuiPortal>
<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>
</>
);
});
Original file line number Diff line number Diff line change
@@ -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<Props> = ({ noticePath, onClose }) => {
const { notifications } = useStartServices();
const [notice, setNotice] = useState<string | undefined>(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 (
<EuiModal maxWidth={true} onClose={onClose}>
<EuiModalHeader>
<EuiModalHeaderTitle>
<h1>NOTICE.txt</h1>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiCodeBlock overflowHeight={360}>
{notice ? (
notice
) : (
// Simulate a long notice while loading
<>
<p>
<EuiLoadingContent lines={5} />
</p>
<p>
<EuiLoadingContent lines={6} />
</p>
</>
)}
</EuiCodeBlock>
</EuiModalBody>
<EuiModalFooter>
<EuiButton color="primary" fill onClick={onClose}>
<FormattedMessage id="xpack.fleet.epm.noticeModalCloseBtn" defaultMessage="Close" />
</EuiButton>
</EuiModalFooter>
</EuiModal>
);
};
7 changes: 7 additions & 0 deletions x-pack/plugins/fleet/server/services/epm/archive/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/services/epm/packages/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand Down
12 changes: 12 additions & 0 deletions x-pack/plugins/fleet/server/services/epm/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}