diff --git a/x-pack/plugins/fleet/common/constants/plugin.ts b/x-pack/plugins/fleet/common/constants/plugin.ts index f9f71fb1608fa..2efb4ab11b949 100644 --- a/x-pack/plugins/fleet/common/constants/plugin.ts +++ b/x-pack/plugins/fleet/common/constants/plugin.ts @@ -8,3 +8,4 @@ export const PLUGIN_ID = 'fleet' as const; export const INTEGRATIONS_PLUGIN_ID = 'integrations' as const; export const TRANSFORM_PLUGIN_ID = 'transform' as const; +export const ELASTICSEARCH_PLUGIN_ID = 'elasticsearch' as const; diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 36a83c1c0a09e..bcae5230aab52 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -207,6 +207,15 @@ export interface DeploymentsModes { default?: DeploymentsModesDefault; } +type Action = 'action'; +type NextStep = 'next_step'; +export interface ConfigurationLink { + title: string; + url: string; + type: Action | NextStep; + content?: string; +} + export enum RegistryPolicyTemplateKeys { categories = 'categories', data_streams = 'data_streams', @@ -223,6 +232,7 @@ export enum RegistryPolicyTemplateKeys { icons = 'icons', screenshots = 'screenshots', deployment_modes = 'deployment_modes', + configuration_links = 'configuration_links', } interface BaseTemplate { [RegistryPolicyTemplateKeys.name]: string; @@ -232,6 +242,7 @@ interface BaseTemplate { [RegistryPolicyTemplateKeys.screenshots]?: RegistryImage[]; [RegistryPolicyTemplateKeys.multiple]?: boolean; [RegistryPolicyTemplateKeys.deployment_modes]?: DeploymentsModes; + [RegistryPolicyTemplateKeys.configuration_links]?: ConfigurationLink[]; } export interface RegistryPolicyIntegrationTemplate extends BaseTemplate { [RegistryPolicyTemplateKeys.categories]?: Array; diff --git a/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/index.tsx index 0fbf8d278fab8..da08a8ea0d749 100644 --- a/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/index.tsx @@ -185,6 +185,7 @@ export const AgentlessEnrollmentFlyout = ({ ) : ( diff --git a/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/next_steps.tsx b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/next_steps.tsx new file mode 100644 index 0000000000000..e068dc5311ea7 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/next_steps.tsx @@ -0,0 +1,150 @@ +/* + * 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, { useCallback, useMemo } from 'react'; +import { + EuiSpacer, + EuiFlexItem, + EuiCard, + EuiFlexGroup, + EuiButton, + EuiHorizontalRule, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { useStartServices } from '../../hooks'; +import type { PackagePolicy, RegistryPolicyTemplate } from '../../types'; +import { ELASTICSEARCH_PLUGIN_ID } from '../../../common/constants/plugin'; + +export const NextSteps = ({ + packagePolicy, + policyTemplates, +}: { + packagePolicy: PackagePolicy; + policyTemplates?: RegistryPolicyTemplate[]; +}) => { + const { application } = useStartServices(); + + const configurationLinks = useMemo(() => { + if (policyTemplates) { + return policyTemplates + ?.filter( + (template) => template?.configuration_links && template.configuration_links.length > 0 + ) + .flatMap((template) => template.configuration_links); + } + return []; + }, [policyTemplates]); + + const parseKbnLink = (url: string) => { + // matching strings with format kbn:/app/appId/path/optionalsubpath + const matches = url.match(/kbn:\/app\/(\w*)\/(\w*\/*)*/); + if (matches && matches.length > 0) { + const appId = matches[1]; + const path = matches[2]; + return { appId, path }; + } + return undefined; + }; + + const isExternal = (url: string) => url.startsWith('http') || url.startsWith('https'); + const onClickLink = useCallback( + (url?: string) => { + if (!url) return undefined; + + if (isExternal(url)) { + application.navigateToUrl(`${url}`); + } else if (url.startsWith('kbn:/')) { + const parsedLink = parseKbnLink(url); + if (parsedLink) { + const { appId, path } = parsedLink; + application.navigateToApp(appId, { + path, + }); + } + } + }, + [application] + ); + + const nextStepsCards = configurationLinks + .filter((link) => link?.type === 'next_step') + .map((link, index) => { + return ( + + onClickLink(link?.url)} + /> + + ); + }); + + const connectorCards = packagePolicy.inputs + .filter((input) => !!input?.vars?.connector_id.value || !!input?.vars?.connector_name.value) + .map((input, index) => { + return ( + + { + application.navigateToApp(ELASTICSEARCH_PLUGIN_ID, { + path: input?.vars?.connector_id.value + ? `content/connectors/${input?.vars?.connector_id.value}` + : `content/connectors`, + }); + }} + /> + + ); + }); + + const actionButtons = configurationLinks + .filter((link) => !!link && link?.type === 'action') + .map((link, index) => { + return ( + + onClickLink(link?.url)} + > + {link?.title} + + + ); + }); + + return ( + <> + + {nextStepsCards.length > 0 && ( + + {nextStepsCards} + {connectorCards} + + )} + + + {actionButtons.length > 0 && ( + + {actionButtons} + + )} + + ); +}; diff --git a/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/step_confirm_data.tsx b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/step_confirm_data.tsx index 34e69d8ef839a..0775716eefd1b 100644 --- a/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/step_confirm_data.tsx +++ b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/step_confirm_data.tsx @@ -12,20 +12,24 @@ import type { EuiStepStatus } from '@elastic/eui'; import { EuiText, EuiLink, EuiSpacer, EuiCallOut } from '@elastic/eui'; import { useStartServices } from '../../hooks'; -import type { Agent, PackagePolicy } from '../../types'; +import type { Agent, PackagePolicy, RegistryPolicyTemplate } from '../../types'; import { usePollingIncomingData, POLLING_TIMEOUT_MS, } from '../agent_enrollment_flyout/use_get_agent_incoming_data'; +import { NextSteps } from './next_steps'; + export const AgentlessStepConfirmData = ({ agent, packagePolicy, setConfirmDataStatus, + policyTemplates, }: { agent: Agent; packagePolicy: PackagePolicy; setConfirmDataStatus: (status: EuiStepStatus) => void; + policyTemplates?: RegistryPolicyTemplate[]; }) => { const { docLinks } = useStartServices(); const [overallState, setOverallState] = useState<'pending' | 'success' | 'failure'>('pending'); @@ -53,13 +57,17 @@ export const AgentlessStepConfirmData = ({ if (overallState === 'success') { return ( - + <> + + + + ); } else if (overallState === 'failure') { return ( diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index e960a7b955fea..384c7b1252554 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -271,7 +271,7 @@ export async function getIncomingDataByAgentsId({ } catch (error) { logger.debug(`Error getting incoming data for agents: ${error}`); throw new FleetError( - `Unable to retrive incoming data for agents due to error: ${error.message}` + `Unable to retrieve incoming data for agents due to error: ${error.message}` ); } }