diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx index 27e17f6b3df610..75a67fb9288e55 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx @@ -3,7 +3,7 @@ * 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, useState } from 'react'; +import React, { memo, useState, useMemo } from 'react'; import { EuiPortal, EuiContextMenuItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { Agent } from '../../../../types'; @@ -14,16 +14,26 @@ import { useAgentRefresh } from '../hooks'; export const AgentDetailsActionMenu: React.FunctionComponent<{ agent: Agent; -}> = memo(({ agent }) => { + assignFlyoutOpenByDefault?: boolean; + onCancelReassign?: () => void; +}> = memo(({ agent, assignFlyoutOpenByDefault = false, onCancelReassign }) => { const hasWriteCapabilites = useCapabilities().write; const refreshAgent = useAgentRefresh(); - const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(false); + const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(assignFlyoutOpenByDefault); + + const onClose = useMemo(() => { + if (onCancelReassign) { + return onCancelReassign; + } else { + return () => setIsReassignFlyoutOpen(false); + } + }, [onCancelReassign, setIsReassignFlyoutOpen]); return ( <> {isReassignFlyoutOpen && ( - setIsReassignFlyoutOpen(false)} /> + )} = memo(({ agent, agentConfig }) => { const { getHref } = useLink(); + return ( {[ diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx index 5bb0464801f70e..15086879ce80b3 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx @@ -3,8 +3,8 @@ * 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, { useMemo } from 'react'; -import { useRouteMatch, Switch, Route } from 'react-router-dom'; +import React, { useMemo, useCallback } from 'react'; +import { useRouteMatch, Switch, Route, useLocation } from 'react-router-dom'; import styled from 'styled-components'; import { EuiFlexGroup, @@ -19,14 +19,21 @@ import { import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { Agent, AgentConfig } from '../../../types'; +import { Agent, AgentConfig, AgentDetailsReassignConfigAction } from '../../../types'; import { PAGE_ROUTING_PATHS } from '../../../constants'; import { Loading, Error } from '../../../components'; -import { useGetOneAgent, useGetOneAgentConfig, useLink, useBreadcrumbs } from '../../../hooks'; +import { + useGetOneAgent, + useGetOneAgentConfig, + useLink, + useBreadcrumbs, + useCore, +} from '../../../hooks'; import { WithHeaderLayout } from '../../../layouts'; import { AgentHealth } from '../components'; import { AgentRefreshContext } from './hooks'; import { AgentEventsTable, AgentDetailsActionMenu, AgentDetailsContent } from './components'; +import { useIntraAppState } from '../../../hooks/use_intra_app_state'; const Divider = styled.div` width: 0; @@ -54,6 +61,19 @@ export const AgentDetailsPage: React.FunctionComponent = () => { sendRequest: sendAgentConfigRequest, } = useGetOneAgentConfig(agentData?.item?.config_id); + const { + application: { navigateToApp }, + } = useCore(); + const routeState = useIntraAppState(); + const queryParams = new URLSearchParams(useLocation().search); + const openReassignFlyoutOpenByDefault = queryParams.get('openReassignFlyout') === 'true'; + + const reassignCancelClickHandler = useCallback(() => { + if (routeState && routeState.onDoneNavigateTo) { + navigateToApp(routeState.onDoneNavigateTo[0], routeState.onDoneNavigateTo[1]); + } + }, [routeState, navigateToApp]); + const headerLeftContent = useMemo( () => ( @@ -124,7 +144,17 @@ export const AgentDetailsPage: React.FunctionComponent = () => { }, { isDivider: true }, { - content: , + content: ( + + ), }, ].map((item, index) => ( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts index b2948686ff6e57..c5833adcded5f9 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts @@ -29,9 +29,18 @@ export interface AgentConfigDetailsDeployAgentAction { onDoneNavigateTo?: Parameters; } +/** + * Supported routing state for the agent config details page routes with deploy agents action + */ +export interface AgentDetailsReassignConfigAction { + /** On done, navigate to the given app */ + onDoneNavigateTo?: Parameters; +} + /** * All possible Route states. */ export type AnyIntraAppRouteState = | CreateDatasourceRouteState - | AgentConfigDetailsDeployAgentAction; + | AgentConfigDetailsDeployAgentAction + | AgentDetailsReassignConfigAction; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx index b7e90c19799c76..fdd122ab3479ae 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx @@ -19,7 +19,8 @@ import React, { memo, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { HostMetadata } from '../../../../../../common/endpoint/types'; -import { useHostSelector, useHostLogsUrl, useHostIngestUrl } from '../hooks'; +import { useHostSelector, useHostLogsUrl, useAgentDetailsIngestUrl } from '../hooks'; +import { useNavigateToAppEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; import { policyResponseStatus, uiQueryParams } from '../../store/selectors'; import { POLICY_STATUS_TO_HEALTH_COLOR } from '../host_constants'; import { FormattedDateAndTime } from '../../../../../common/components/endpoint/formatted_date_time'; @@ -28,6 +29,7 @@ import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app import { getEndpointDetailsPath, getPolicyDetailPath } from '../../../../common/routing'; import { SecurityPageName } from '../../../../../app/types'; import { useFormatUrl } from '../../../../../common/components/link_to'; +import { AgentDetailsReassignConfigAction } from '../../../../../../../ingest_manager/public'; const HostIds = styled(EuiListGroupItem)` margin-top: 0; @@ -46,9 +48,16 @@ const LinkToExternalApp = styled.div` } `; +const openReassignFlyoutSearch = '?openReassignFlyout=true'; + export const HostDetails = memo(({ details }: { details: HostMetadata }) => { const { url: logsUrl, appId: logsAppId, appPath: logsAppPath } = useHostLogsUrl(details.host.id); - const { url: ingestUrl, appId: ingestAppId, appPath: ingestAppPath } = useHostIngestUrl(); + const agentId = details.elastic.agent.id; + const { + url: agentDetailsUrl, + appId: ingestAppId, + appPath: agentDetailsAppPath, + } = useAgentDetailsIngestUrl(agentId); const queryParams = useHostSelector(uiQueryParams); const policyStatus = useHostSelector( policyResponseStatus @@ -96,6 +105,22 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { ]; }, [details.host.id, formatUrl, queryParams]); + const agentDetailsWithFlyoutPath = `${agentDetailsAppPath}${openReassignFlyoutSearch}`; + const agentDetailsWithFlyoutUrl = `${agentDetailsUrl}${openReassignFlyoutSearch};`; + const handleReassignEndpointsClick = useNavigateToAppEventHandler< + AgentDetailsReassignConfigAction + >(ingestAppId, { + path: agentDetailsWithFlyoutPath, + state: { + onDoneNavigateTo: [ + 'securitySolution:management', + { + path: getEndpointDetailsPath({ name: 'endpointDetails', selected_host: details.host.id }), + }, + ], + }, + }); + const policyStatusClickHandler = useNavigateByRouterEventHandler(policyResponseRoutePath); const [policyDetailsRoutePath, policyDetailsRouteUrl] = useMemo(() => { @@ -207,8 +232,9 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts index 51aaea20df8431..c072c812edbb5d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts @@ -51,3 +51,20 @@ export const useHostIngestUrl = (): { url: string; appId: string; appPath: strin }; }, [services.application]); }; + +/** + * Returns an object that contains Ingest app and URL information + */ +export const useAgentDetailsIngestUrl = ( + agentId: string +): { url: string; appId: string; appPath: string } => { + const { services } = useKibana(); + return useMemo(() => { + const appPath = `#/fleet/agents/${agentId}/activity`; + return { + url: `${services.application.getUrlForApp('ingestManager')}${appPath}`, + appId: 'ingestManager', + appPath, + }; + }, [services.application, agentId]); +};