diff --git a/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts b/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts index ecd4227a54b655b..e5e075bb363213a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts @@ -28,7 +28,7 @@ export type DynamicPage = export type Page = StaticPage | DynamicPage; export interface DynamicPagePathValues { - [key: string]: string; + [key: string]: string | undefined; } export const BASE_PATH = '/app/fleet'; @@ -78,8 +78,8 @@ export const pagePathGetters: { `/policies/${policyId}/edit-integration/${packagePolicyId}`, fleet: () => '/fleet', fleet_agent_list: ({ kuery }) => `/fleet/agents${kuery ? `?kuery=${kuery}` : ''}`, - fleet_agent_details: ({ agentId, tabId }) => - `/fleet/agents/${agentId}${tabId ? `/${tabId}` : ''}`, + fleet_agent_details: ({ agentId, tabId, query }) => + `/fleet/agents/${agentId}${tabId ? `/${tabId}` : ''}${query ? `?${query}` : ''}`, fleet_enrollment_tokens: () => '/fleet/enrollment-tokens', data_streams: () => '/data-streams', }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx index 487eac6779dd550..2b1eb8e1ce984ad 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx @@ -73,7 +73,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ )} = memo(({ agent, agentPolicy }) => { - const { getHref } = useLink(); - const kibanaVersion = useKibanaVersion(); - return ( - - {[ - { - title: i18n.translate('xpack.fleet.agentDetails.hostNameLabel', { - defaultMessage: 'Host name', - }), - description: - typeof agent.local_metadata.host === 'object' && - typeof agent.local_metadata.host.hostname === 'string' - ? agent.local_metadata.host.hostname - : '-', - }, - { - title: i18n.translate('xpack.fleet.agentDetails.hostIdLabel', { - defaultMessage: 'Agent ID', - }), - description: agent.id, - }, - { - title: i18n.translate('xpack.fleet.agentDetails.statusLabel', { - defaultMessage: 'Status', - }), - description: , - }, - { - title: i18n.translate('xpack.fleet.agentDetails.agentPolicyLabel', { - defaultMessage: 'Agent policy', - }), - description: agentPolicy ? ( - - {agentPolicy.name || agent.policy_id} - - ) : ( - agent.policy_id || '-' - ), - }, - { - title: i18n.translate('xpack.fleet.agentDetails.versionLabel', { - defaultMessage: 'Agent version', - }), - description: - typeof agent.local_metadata.elastic === 'object' && - typeof agent.local_metadata.elastic.agent === 'object' && - typeof agent.local_metadata.elastic.agent.version === 'string' ? ( - - - {agent.local_metadata.elastic.agent.version} - - {isAgentUpgradeable(agent, kibanaVersion) ? ( - - - -   - - - - ) : null} - - ) : ( - '-' - ), - }, - { - title: i18n.translate('xpack.fleet.agentDetails.releaseLabel', { - defaultMessage: 'Agent release', - }), - description: - typeof agent.local_metadata.elastic === 'object' && - typeof agent.local_metadata.elastic.agent === 'object' && - typeof agent.local_metadata.elastic.agent.snapshot === 'boolean' - ? agent.local_metadata.elastic.agent.snapshot === true - ? 'snapshot' - : 'stable' - : '-', - }, - { - title: i18n.translate('xpack.fleet.agentDetails.logLevel', { - defaultMessage: 'Log level', - }), - description: - typeof agent.local_metadata.elastic === 'object' && - typeof agent.local_metadata.elastic.agent === 'object' && - typeof agent.local_metadata.elastic.agent.log_level === 'string' - ? agent.local_metadata.elastic.agent.log_level - : '-', - }, - { - title: i18n.translate('xpack.fleet.agentDetails.platformLabel', { - defaultMessage: 'Platform', - }), - description: - typeof agent.local_metadata.os === 'object' && - typeof agent.local_metadata.os.platform === 'string' - ? agent.local_metadata.os.platform - : '-', - }, - ].map(({ title, description }) => { - return ( - - - {title} - - - {description} - - - ); - })} - - ); -}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx new file mode 100644 index 000000000000000..6b84c8fe6b0f90c --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { memo } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiAccordion, + EuiTitle, + EuiPanel, + EuiButtonIcon, + EuiBasicTable, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import styled from 'styled-components'; +import { Agent, AgentPolicy, PackagePolicy, PackagePolicyInput } from '../../../../../types'; +import { useLink } from '../../../../../hooks'; +import { PackageIcon } from '../../../../../components'; +import { + DATASET_FIELD, + AGENT_DATASET_FILEBEAT, + AGENT_DATASET_METRICBEAT, +} from '../agent_logs/constants'; +import { displayInputType, getLogsQueryByInputType } from './input_type_utils'; + +const StyledCollapsablePanel = styled(EuiPanel).attrs((props) => ({ + paddingSize: 'none', +}))` + .euiAccordion__triggerWrapper { + border-bottom: 1px solid ${(props) => props.theme.eui.euiColorLightShade}; + padding: ${(props) => props.theme.eui.paddingSizes.m} + ${(props) => props.theme.eui.paddingSizes.m}; + } + .euiAccordion__childWrapper { + overflow: visible; + } +`; + +const CollapsablePanel: React.FC<{ title: React.ReactNode }> = ({ title, children }) => { + return ( + + + {children} + + + ); +}; + +export const AgentDetailsIntegrationsSection: React.FunctionComponent<{ + agent: Agent; + agentPolicy?: AgentPolicy; +}> = memo(({ agent, agentPolicy }) => { + const { getHref } = useLink(); + if (!agentPolicy || !agentPolicy.package_policies) { + return null; + } + + const columns = [ + { + field: 'type', + width: '100%', + name: i18n.translate('xpack.fleet.agentDetailsIntegrations.inputTypeLabel', { + defaultMessage: 'Input', + }), + render: (inputType: string) => { + return displayInputType(inputType); + }, + }, + { + name: i18n.translate('xpack.fleet.agentDetailsIntegrations.actionsLabel', { + defaultMessage: 'Actions', + }), + field: 'type', + width: 'auto', + render: (inputType: string) => { + return ( + + ); + }, + }, + ]; + + return ( + + {(agentPolicy.package_policies as PackagePolicy[]).map((packagePolicy) => { + if (!packagePolicy.package) { + return null; + } + + return ( + + +

+ + + + + + + {packagePolicy.name} + + + +

+ + } + > + + tableLayout="auto" + items={packagePolicy.inputs} + columns={columns} + /> +
+
+ ); + })} +
+ ); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx new file mode 100644 index 000000000000000..745a19af6a89f56 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { memo } from 'react'; +import { + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiText } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Agent, AgentPolicy } from '../../../../../types'; +import { useKibanaVersion, useLink } from '../../../../../hooks'; +import { isAgentUpgradeable } from '../../../../../services'; +import { AgentPolicyPackageBadges } from '../../../components/agent_policy_package_badges'; +import { LinkAndRevision } from '../../../../../components'; + +export const AgentDetailsOverviewSection: React.FunctionComponent<{ + agent: Agent; + agentPolicy?: AgentPolicy; +}> = memo(({ agent, agentPolicy }) => { + const { getHref } = useLink(); + const kibanaVersion = useKibanaVersion(); + return ( + + + {[ + { + title: i18n.translate('xpack.fleet.agentDetails.hostIdLabel', { + defaultMessage: 'Agent ID', + }), + description: agent.id, + }, + { + title: i18n.translate('xpack.fleet.agentDetails.agentPolicyLabel', { + defaultMessage: 'Agent policy', + }), + description: agentPolicy ? ( + + {agentPolicy.name || agentPolicy.id} + + ) : ( + agent.policy_id || '-' + ), + }, + { + title: i18n.translate('xpack.fleet.agentDetails.versionLabel', { + defaultMessage: 'Agent version', + }), + description: + typeof agent.local_metadata.elastic === 'object' && + typeof agent.local_metadata.elastic.agent === 'object' && + typeof agent.local_metadata.elastic.agent.version === 'string' ? ( + + + {agent.local_metadata.elastic.agent.version} + + {isAgentUpgradeable(agent, kibanaVersion) ? ( + + + +   + + + + ) : null} + + ) : ( + '-' + ), + }, + { + title: i18n.translate('xpack.fleet.agentDetails.enrollmentTokenLabel', { + defaultMessage: 'Enrollment token', + }), + description: '-', // Fixme when we have the enrollment tokenhttps://github.com/elastic/kibana/issues/61269 + }, + { + title: i18n.translate('xpack.fleet.agentDetails.integrationsLabel', { + defaultMessage: 'Integrations', + }), + description: agent.policy_id ? ( + + ) : null, + }, + { + title: i18n.translate('xpack.fleet.agentDetails.hostNameLabel', { + defaultMessage: 'Host name', + }), + description: + typeof agent.local_metadata.host === 'object' && + typeof agent.local_metadata.host.hostname === 'string' + ? agent.local_metadata.host.hostname + : '-', + }, + { + title: i18n.translate('xpack.fleet.agentDetails.logLevel', { + defaultMessage: 'Logging level', + }), + description: + typeof agent.local_metadata.elastic === 'object' && + typeof agent.local_metadata.elastic.agent === 'object' && + typeof agent.local_metadata.elastic.agent.log_level === 'string' + ? agent.local_metadata.elastic.agent.log_level + : '-', + }, + { + title: i18n.translate('xpack.fleet.agentDetails.releaseLabel', { + defaultMessage: 'Agent release', + }), + description: + typeof agent.local_metadata.elastic === 'object' && + typeof agent.local_metadata.elastic.agent === 'object' && + typeof agent.local_metadata.elastic.agent.snapshot === 'boolean' + ? agent.local_metadata.elastic.agent.snapshot === true + ? 'snapshot' + : 'stable' + : '-', + }, + { + title: i18n.translate('xpack.fleet.agentDetails.platformLabel', { + defaultMessage: 'Platform', + }), + description: + typeof agent.local_metadata.os === 'object' && + typeof agent.local_metadata.os.platform === 'string' + ? agent.local_metadata.os.platform + : '-', + }, + { + title: i18n.translate('xpack.fleet.agentDetails.monitorLogsLabel', { + defaultMessage: 'Monitor logs', + }), + description: agentPolicy?.monitoring_enabled?.includes('logs') ? ( + + ) : ( + + ), + }, + { + title: i18n.translate('xpack.fleet.agentDetails.monitorMetricsLabel', { + defaultMessage: 'Monitor metrics', + }), + description: agentPolicy?.monitoring_enabled?.includes('metrics') ? ( + + ) : ( + + ), + }, + ].map(({ title, description }) => { + return ( + + + + {title} + + + {description} + + + + ); + })} + + + ); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/index.tsx new file mode 100644 index 000000000000000..0b83fb4cc64e1bb --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/index.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { memo } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Agent, AgentPolicy } from '../../../../../types'; +import { AgentDetailsOverviewSection } from './agent_details_overview'; +import { AgentDetailsIntegrationsSection } from './agent_details_integrations'; + +export const AgentDetailsContent: React.FunctionComponent<{ + agent: Agent; + agentPolicy?: AgentPolicy; +}> = memo(({ agent, agentPolicy }) => { + return ( + <> + + + +

+ +

+
+ + +
+ + +

+ +

+
+ + +
+
+ + ); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/input_type_utils.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/input_type_utils.ts new file mode 100644 index 000000000000000..07dee3cac35afdc --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/input_type_utils.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { + DATASET_FIELD, + AGENT_DATASET_FILEBEAT, + AGENT_DATASET_METRICBEAT, +} from '../agent_logs/constants'; + +export function displayInputType(inputType: string): string { + if (inputType === 'logfile') { + return i18n.translate('xpack.fleet.agentDetailsIntegrations.inputTypeLogText', { + defaultMessage: 'Logs', + }); + } + if (inputType === 'endpoint') { + return i18n.translate('xpack.fleet.agentDetailsIntegrations.inputTypeEndpointText', { + defaultMessage: 'Endpoint', + }); + } + if (inputType.match(/\/metrics$/)) { + return i18n.translate('xpack.fleet.agentDetailsIntegrations.inputTypeEndpointText', { + defaultMessage: 'Metrics', + }); + } + + return inputType; +} + +export function getLogsQueryByInputType(inputType: string) { + if (inputType === 'logfile') { + return `_q=(${DATASET_FIELD.name}:!(${AGENT_DATASET_FILEBEAT}))`; + } + if (inputType.match(/\/metrics$/)) { + return `_q=(${DATASET_FIELD.name}:!(${AGENT_DATASET_METRICBEAT}))`; + } +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/constants.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/constants.tsx index 89fe1a916605de5..f7fedeeedae2f76 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/constants.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/constants.tsx @@ -7,6 +7,8 @@ import { AgentLogsState } from './agent_logs'; export const AGENT_LOG_INDEX_PATTERN = 'logs-elastic_agent-*,logs-elastic_agent.*-*'; export const AGENT_DATASET = 'elastic_agent'; +export const AGENT_DATASET_FILEBEAT = 'elastic_agent.filebeat'; +export const AGENT_DATASET_METRICBEAT = 'elastic_agent.metricbeat'; export const AGENT_DATASET_PATTERN = 'elastic_agent.*'; export const AGENT_ID_FIELD = { name: 'elastic_agent.id', diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts index 128f803bb2f2e15..0e1d98f89b1f0e0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts @@ -5,4 +5,4 @@ */ export { AgentLogs } from './agent_logs'; export { AgentDetailsActionMenu } from './actions_menu'; -export { AgentDetailsContent } from './agent_details'; +export { AgentDetailsContent } from './agent_details/'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx index f3714bbb532236e..f887f3573b85e24 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx @@ -17,7 +17,7 @@ import { EuiDescriptionListDescription, } from '@elastic/eui'; import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiIconTip } from '@elastic/eui'; import { Agent, AgentPolicy, AgentDetailsReassignPolicyAction } from '../../../types'; @@ -78,6 +78,8 @@ export const AgentDetailsPage: React.FunctionComponent = () => { } }, [routeState, navigateToApp]); + const host = agentData?.item?.local_metadata?.host; + const headerLeftContent = useMemo( () => ( @@ -99,9 +101,8 @@ export const AgentDetailsPage: React.FunctionComponent = () => {

{isLoading && isInitialRequest ? ( - ) : typeof agentData?.item?.local_metadata?.host === 'object' && - typeof agentData?.item?.local_metadata?.host?.hostname === 'string' ? ( - agentData.item.local_metadata.host.hostname + ) : typeof host === 'object' && typeof host?.hostname === 'string' ? ( + host.hostname ) : ( { ), - [agentData?.item?.local_metadata?.host, agentId, getHref, isInitialRequest, isLoading] + [host, agentId, getHref, isInitialRequest, isLoading] ); const headerRightContent = useMemo( () => agentData && agentData.item ? ( - + {[ { label: i18n.translate('xpack.fleet.agentDetails.statusLabel', { @@ -130,6 +131,16 @@ export const AgentDetailsPage: React.FunctionComponent = () => { }), content: , }, + { + label: i18n.translate('xpack.fleet.agentDetails.lastActivityLabel', { + defaultMessage: 'Last activity', + }), + content: agentData.item.last_checkin ? ( + + ) : ( + '-' + ), + }, { isDivider: true }, { label: i18n.translate('xpack.fleet.agentDetails.policyLabel', { @@ -201,20 +212,22 @@ export const AgentDetailsPage: React.FunctionComponent = () => { /> ), }, - ].map((item, index) => ( - - {item.isDivider ?? false ? ( - - ) : item.label ? ( - - {item.label} - {item.content} - - ) : ( - item.content - )} - - ))} + ] + .filter((item) => item.isDivider !== true) + .map((item, index) => ( + + {item.isDivider ?? false ? ( + + ) : item.label ? ( + + {item.label} + {item.content} + + ) : ( + item.content + )} + + ))} ) : undefined, /* eslint-disable-next-line react-hooks/exhaustive-deps */ diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx index 45017ac8532da7f..40d91f13db65904 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; -import { EuiHealth, EuiToolTip } from '@elastic/eui'; +import { EuiBadge, EuiToolTip } from '@elastic/eui'; import { Agent } from '../../../types'; interface Props { @@ -13,79 +13,52 @@ interface Props { } const Status = { - Online: ( - - - + Healthy: ( + + + ), Offline: ( - + - + ), Inactive: ( - - - - ), - Warning: ( - - - - ), - Error: ( - - - - ), - Degraded: ( - - - - ), - Enrolling: ( - - - + + + ), - Unenrolling: ( - + Unhealthy: ( + - + ), - Upgrading: ( - + Updating: ( + - + ), }; function getStatusComponent(agent: Agent): React.ReactElement { switch (agent.status) { + case 'warning': case 'error': - return Status.Error; case 'degraded': - return Status.Degraded; + return Status.Unhealthy; case 'inactive': return Status.Inactive; case 'offline': return Status.Offline; - case 'warning': - return Status.Warning; case 'unenrolling': - return Status.Unenrolling; case 'enrolling': - return Status.Enrolling; case 'updating': - return Status.Upgrading; + return Status.Updating; default: - return Status.Online; + return Status.Healthy; } } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx index 08835cc872b82a3..413e801471820dc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx @@ -3,53 +3,74 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSpacer, EuiText, EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui'; -import { PackagePolicy } from '../../../types'; +import { PackagePolicy, PackagePolicyPackage } from '../../../types'; import { useGetOneAgentPolicy } from '../../../hooks'; import { PackageIcon } from '../../../components/package_icon'; interface Props { agentPolicyId: string; + hideTitle?: boolean; } -export const AgentPolicyPackageBadges: React.FunctionComponent = ({ agentPolicyId }) => { +export const AgentPolicyPackageBadges: React.FunctionComponent = ({ + agentPolicyId, + hideTitle, +}) => { const agentPolicyRequest = useGetOneAgentPolicy(agentPolicyId); const agentPolicy = agentPolicyRequest.data ? agentPolicyRequest.data.item : null; + const packages = useMemo(() => { + if (!agentPolicy) { + return null; + } + + const uniquePackages = new Map(); + + (agentPolicy.package_policies as PackagePolicy[]).forEach(({ package: pkg }) => { + if (!pkg) { + return; + } + + if (!uniquePackages.has(pkg.name) || uniquePackages.get(pkg.name)!.version < pkg.version) { + uniquePackages.set(pkg.name, pkg); + } + }); + + return [...uniquePackages.values()]; + }, [agentPolicy]); + if (!agentPolicy) { return null; } + return ( <> - - {agentPolicy.package_policies.length}, - }} - /> - - - {(agentPolicy.package_policies as PackagePolicy[]).map((packagePolicy, idx) => { - if (!packagePolicy.package) { - return null; - } + {!hideTitle && ( + <> + + {packages.length}, + }} + /> + + + + )} + {packages.map((pkg, idx) => { return ( - + - {packagePolicy.package.title} + {pkg.title} ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/types/index.ts b/x-pack/plugins/fleet/public/applications/fleet/types/index.ts index ded1447954aff7b..dd80c1ad77b85d3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/types/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/types/index.ts @@ -22,6 +22,7 @@ export { NewPackagePolicyInputStream, PackagePolicyConfigRecord, PackagePolicyConfigRecordEntry, + PackagePolicyPackage, Output, DataStream, // API schema - misc setup, status