diff --git a/x-pack/plugins/ingest_manager/common/constants/routes.ts b/x-pack/plugins/ingest_manager/common/constants/routes.ts index abb266da9f066..3309d8497f4c5 100644 --- a/x-pack/plugins/ingest_manager/common/constants/routes.ts +++ b/x-pack/plugins/ingest_manager/common/constants/routes.ts @@ -46,6 +46,7 @@ export const AGENT_CONFIG_API_ROUTES = { UPDATE_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}`, DELETE_PATTERN: `${AGENT_CONFIG_API_ROOT}/delete`, FULL_INFO_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/full`, + FULL_INFO_DOWNLOAD_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/download`, }; // Output API routes diff --git a/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts b/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts new file mode 100644 index 0000000000000..9dfd76b9ddd21 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts @@ -0,0 +1,39 @@ +/* + * 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 { safeDump } from 'js-yaml'; +import { FullAgentConfig } from '../types'; + +const CONFIG_KEYS_ORDER = [ + 'id', + 'name', + 'revision', + 'type', + 'outputs', + 'settings', + 'datasources', + 'enabled', + 'package', + 'input', +]; + +export const configToYaml = (config: FullAgentConfig): string => { + return safeDump(config, { + skipInvalid: true, + sortKeys: (keyA: string, keyB: string) => { + const indexA = CONFIG_KEYS_ORDER.indexOf(keyA); + const indexB = CONFIG_KEYS_ORDER.indexOf(keyB); + if (indexA >= 0 && indexB < 0) { + return -1; + } + + if (indexA < 0 && indexB >= 0) { + return 1; + } + + return indexA - indexB; + }, + }); +}; diff --git a/x-pack/plugins/ingest_manager/common/services/index.ts b/x-pack/plugins/ingest_manager/common/services/index.ts index 91dbbdd515c3e..c595c9a52f66f 100644 --- a/x-pack/plugins/ingest_manager/common/services/index.ts +++ b/x-pack/plugins/ingest_manager/common/services/index.ts @@ -8,5 +8,6 @@ import * as AgentStatusKueryHelper from './agent_status'; export * from './routes'; export { packageToConfigDatasourceInputs, packageToConfigDatasource } from './package_to_config'; export { storedDatasourceToAgentDatasource } from './datasource_to_agent_datasource'; +export { configToYaml } from './config_to_yaml'; export { AgentStatusKueryHelper }; export { decodeCloudId } from './decode_cloud_id'; diff --git a/x-pack/plugins/ingest_manager/common/services/routes.ts b/x-pack/plugins/ingest_manager/common/services/routes.ts index 20d040ac6eaee..3fc990ea9d70c 100644 --- a/x-pack/plugins/ingest_manager/common/services/routes.ts +++ b/x-pack/plugins/ingest_manager/common/services/routes.ts @@ -90,6 +90,13 @@ export const agentConfigRouteService = { getInfoFullPath: (agentConfigId: string) => { return AGENT_CONFIG_API_ROUTES.FULL_INFO_PATTERN.replace('{agentConfigId}', agentConfigId); }, + + getInfoFullDownloadPath: (agentConfigId: string) => { + return AGENT_CONFIG_API_ROUTES.FULL_INFO_DOWNLOAD_PATTERN.replace( + '{agentConfigId}', + agentConfigId + ); + }, }; export const dataStreamRouteService = { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx new file mode 100644 index 0000000000000..8a9f0553895a1 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx @@ -0,0 +1,68 @@ +/* + * 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, { useCallback, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButtonIcon, + EuiContextMenu, + EuiContextMenuPanel, + EuiPopover, + EuiButton, +} from '@elastic/eui'; +import { EuiButtonProps } from '@elastic/eui/src/components/button/button'; +import { EuiContextMenuProps } from '@elastic/eui/src/components/context_menu/context_menu'; +import { EuiContextMenuPanelProps } from '@elastic/eui/src/components/context_menu/context_menu_panel'; + +type Props = { + button?: { + props: EuiButtonProps; + children: JSX.Element; + }; +} & ( + | { + items: EuiContextMenuPanelProps['items']; + } + | { + panels: EuiContextMenuProps['panels']; + } +); + +export const ContextMenuActions = React.memo(({ button, ...props }) => { + const [isOpen, setIsOpen] = useState(false); + const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); + const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); + + return ( + + {button.children} + + ) : ( + + ) + } + isOpen={isOpen} + closePopover={handleCloseMenu} + > + {'items' in props ? ( + + ) : ( + + )} + + ); +}); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts index b0b4e79cece79..93bc0645c7eee 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts @@ -7,4 +7,7 @@ export { Loading } from './loading'; export { Error } from './error'; export { Header, HeaderProps } from './header'; export { AlphaMessaging } from './alpha_messaging'; +export { PackageIcon } from './package_icon'; +export { ContextMenuActions } from './context_menu_actions'; +export { SearchBar } from './search_bar'; export * from './settings_flyout'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx deleted file mode 100644 index 56f010e2fa774..0000000000000 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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, { useCallback, useState } from 'react'; -import { EuiButtonIcon, EuiContextMenu, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { EuiContextMenuProps } from '@elastic/eui/src/components/context_menu/context_menu'; - -export const TableRowActionsNested = React.memo<{ panels: EuiContextMenuProps['panels'] }>( - ({ panels }) => { - const [isOpen, setIsOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); - const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); - - return ( - - } - isOpen={isOpen} - closePopover={handleCloseMenu} - > - - - ); - } -); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts index 73771fa3cb343..5ef7f45faec48 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts @@ -43,7 +43,6 @@ export const PAGE_ROUTING_PATHS = { configurations: '/configs', configurations_list: '/configs', configuration_details: '/configs/:configId/:tabId?', - configuration_details_yaml: '/configs/:configId/yaml', configuration_details_settings: '/configs/:configId/settings', add_datasource_from_configuration: '/configs/:configId/add-datasource', add_datasource_from_integration: '/integrations/:pkgkey/add-datasource', diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts index a752ad2a8912b..31a511f2d79dc 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts @@ -10,6 +10,7 @@ export { useConfig, ConfigContext } from './use_config'; export { useSetupDeps, useStartDeps, DepsContext } from './use_deps'; export { useBreadcrumbs } from './use_breadcrumbs'; export { useLink } from './use_link'; +export { useKibanaLink } from './use_kibana_link'; export { usePackageIconType, UsePackageIconType } from './use_package_icon_type'; export { usePagination, Pagination } from './use_pagination'; export { useDebounce } from './use_debounce'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts index f80c468677f48..45ca6047b0d96 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts @@ -14,6 +14,7 @@ import { agentConfigRouteService } from '../../services'; import { GetAgentConfigsResponse, GetOneAgentConfigResponse, + GetFullAgentConfigResponse, CreateAgentConfigRequest, CreateAgentConfigResponse, UpdateAgentConfigRequest, @@ -39,7 +40,7 @@ export const useGetOneAgentConfig = (agentConfigId: string | undefined) => { }; export const useGetOneAgentConfigFull = (agentConfigId: string) => { - return useRequest({ + return useRequest({ path: agentConfigRouteService.getInfoFullPath(agentConfigId), method: 'get', }); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx new file mode 100644 index 0000000000000..78ed228012691 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx @@ -0,0 +1,69 @@ +/* + * 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, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiContextMenuItem, EuiPortal } from '@elastic/eui'; +import { useCapabilities, useLink } from '../../../hooks'; +import { ContextMenuActions } from '../../../components'; +import { ConfigYamlFlyout } from './config_yaml_flyout'; + +export const AgentConfigActionMenu = memo<{ configId: string; fullButton?: boolean }>( + ({ configId, fullButton = false }) => { + const { getHref } = useLink(); + const hasWriteCapabilities = useCapabilities().write; + const [isYamlFlyoutOpen, setIsYamlFlyoutOpen] = useState(false); + return ( + <> + {isYamlFlyoutOpen ? ( + + setIsYamlFlyoutOpen(false)} /> + + ) : null} + + ), + } + : undefined + } + items={[ + setIsYamlFlyoutOpen(!isYamlFlyoutOpen)} + key="viewConfig" + > + + , + + + , + ]} + /> + + ); + } +); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_yaml_flyout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_yaml_flyout.tsx new file mode 100644 index 0000000000000..6cf60fe1dc507 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_yaml_flyout.tsx @@ -0,0 +1,99 @@ +/* + * 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 styled from 'styled-components'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiButtonEmpty, + EuiButton, +} from '@elastic/eui'; +import { useGetOneAgentConfigFull, useGetOneAgentConfig, useCore } from '../../../hooks'; +import { Loading } from '../../../components'; +import { configToYaml, agentConfigRouteService } from '../../../services'; + +const FlyoutBody = styled(EuiFlyoutBody)` + .euiFlyoutBody__overflowContent { + padding: 0; + } +`; + +export const ConfigYamlFlyout = memo<{ configId: string; onClose: () => void }>( + ({ configId, onClose }) => { + const core = useCore(); + const { isLoading: isLoadingYaml, data: yamlData } = useGetOneAgentConfigFull(configId); + const { data: configData } = useGetOneAgentConfig(configId); + + const body = + isLoadingYaml && !yamlData ? ( + + ) : ( + + {configToYaml(yamlData!.item)} + + ); + + const downloadLink = core.http.basePath.prepend( + agentConfigRouteService.getInfoFullDownloadPath(configId) + ); + + return ( + + + +

+ {configData?.item ? ( + + ) : ( + + )} +

+
+
+ {body} + + + + + + + + + + + + + + +
+ ); + } +); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts index c1811b99588a8..f3ec15e0f477d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts @@ -3,8 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - export { AgentConfigForm, agentConfigFormValidation } from './config_form'; export { AgentConfigDeleteProvider } from './config_delete_provider'; +export { DatasourceDeleteProvider } from './datasource_delete_provider'; export { LinkedAgentCount } from './linked_agent_count'; export { ConfirmDeployConfigModal } from './confirm_deploy_modal'; +export { DangerEuiContextMenuItem } from './danger_eui_context_menu_item'; +export { AgentConfigActionMenu } from './actions_menu'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx deleted file mode 100644 index 2f9a11ef76704..0000000000000 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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, { useCallback, useState } from 'react'; -import { EuiButtonIcon, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { EuiContextMenuPanelProps } from '@elastic/eui/src/components/context_menu/context_menu_panel'; - -export const TableRowActions = React.memo<{ items: EuiContextMenuPanelProps['items'] }>( - ({ items }) => { - const [isOpen, setIsOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); - const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); - - return ( - - } - isOpen={isOpen} - closePopover={handleCloseMenu} - > - - - ); - } -); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx index 316b7eed491b9..01505fcf4c65e 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx @@ -17,12 +17,10 @@ import { EuiFlexItem, } from '@elastic/eui'; import { AgentConfig, Datasource } from '../../../../../types'; -import { TableRowActions } from '../../../components/table_row_actions'; -import { DangerEuiContextMenuItem } from '../../../components/danger_eui_context_menu_item'; +import { PackageIcon, ContextMenuActions } from '../../../../../components'; +import { DatasourceDeleteProvider, DangerEuiContextMenuItem } from '../../../components'; import { useCapabilities, useLink } from '../../../../../hooks'; -import { DatasourceDeleteProvider } from '../../../components/datasource_delete_provider'; -import { useConfigRefresh } from '../../hooks/use_config'; -import { PackageIcon } from '../../../../../components/package_icon'; +import { useConfigRefresh } from '../../hooks'; interface InMemoryDatasource extends Datasource { streams: { total: number; enabled: number }; @@ -197,7 +195,7 @@ export const DatasourcesTable: React.FunctionComponent = ({ actions: [ { render: (datasource: InMemoryDatasource) => ( - (({ config }) => { - const fullConfigRequest = useGetOneAgentConfigFull(config.id); - - if (fullConfigRequest.isLoading && !fullConfigRequest.data) { - return ; - } - - return ( - - - - {dump(fullConfigRequest.data.item, { - sortKeys: (keyA: string, keyB: string) => { - const indexA = CONFIG_KEYS_ORDER.indexOf(keyA); - const indexB = CONFIG_KEYS_ORDER.indexOf(keyB); - if (indexA >= 0 && indexB < 0) { - return -1; - } - - if (indexA < 0 && indexB >= 0) { - return 1; - } - - return indexA - indexB; - }, - })} - - - - ); -}); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx index 3f886645b5339..1dd7e660deaa9 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx @@ -27,10 +27,8 @@ import { useGetOneAgentConfig, useLink, useBreadcrumbs } from '../../../hooks'; import { Loading } from '../../../components'; import { WithHeaderLayout } from '../../../layouts'; import { ConfigRefreshContext, useGetAgentStatus, AgentStatusRefreshContext } from './hooks'; -import { LinkedAgentCount } from '../components'; -import { ConfigDatasourcesView } from './components/datasources'; -import { ConfigYamlView } from './components/yaml'; -import { ConfigSettingsView } from './components/settings'; +import { LinkedAgentCount, AgentConfigActionMenu } from '../components'; +import { ConfigDatasourcesView, ConfigSettingsView } from './components'; const Divider = styled.div` width: 0; @@ -147,21 +145,31 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { )) || '', }, + { isDivider: true }, + { + content: agentConfig && , + }, ].map((item, index) => ( {item.isDivider ?? false ? ( - ) : ( + ) : item.label ? ( - {item.label} - {item.content} + + {item.label} + + + {item.content} + + ) : ( + item.content )} ))} ), - [agentConfig, agentStatus] + [agentConfig, configId, agentStatus] ); const headerTabs = useMemo(() => { @@ -174,14 +182,6 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { href: getHref('configuration_details', { configId, tabId: 'datasources' }), isSelected: tabId === '' || tabId === 'datasources', }, - { - id: 'yaml', - name: i18n.translate('xpack.ingestManager.configDetails.subTabs.yamlTabText', { - defaultMessage: 'YAML', - }), - href: getHref('configuration_details', { configId, tabId: 'yaml' }), - isSelected: tabId === 'yaml', - }, { id: 'settings', name: i18n.translate('xpack.ingestManager.configDetails.subTabs.settingsTabText', { @@ -254,12 +254,6 @@ const AgentConfigDetailsContent: React.FunctionComponent<{ agentConfig: AgentCon useBreadcrumbs('configuration_details', { configName: agentConfig.name }); return ( - { - return ; - }} - /> { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx index 5b4066e53f0c8..0d43d8856c2fb 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.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, { CSSProperties, memo, useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { EuiSpacer, EuiText, @@ -16,7 +16,6 @@ import { EuiTableActionsColumnType, EuiTableFieldDataColumnType, EuiTextColor, - EuiContextMenuItem, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedDate } from '@kbn/i18n/react'; @@ -33,16 +32,9 @@ import { useUrlParams, useBreadcrumbs, } from '../../../hooks'; +import { SearchBar } from '../../../components'; +import { LinkedAgentCount, AgentConfigActionMenu } from '../components'; import { CreateAgentConfigFlyout } from './components'; -import { SearchBar } from '../../../components/search_bar'; -import { LinkedAgentCount } from '../components'; -import { TableRowActions } from '../components/table_row_actions'; - -const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', -}); const AgentConfigListPageLayout: React.FunctionComponent = ({ children }) => ( ( ); -const ConfigRowActions = memo<{ config: AgentConfig; onDelete: () => void }>( - ({ config, onDelete }) => { - const { getHref } = useLink(); - const hasWriteCapabilities = useCapabilities().write; - - return ( - - - , - - - - , - // - // - // , - ]} - /> - ); - } -); - export const AgentConfigListPage: React.FunctionComponent<{}> = () => { useBreadcrumbs('configurations_list'); const { getHref, getPath } = useLink(); @@ -172,10 +122,10 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { width: '20%', render: (name: string, agentConfig: AgentConfig) => ( - + {name || agentConfig.id} @@ -201,7 +151,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { width: '35%', truncateText: true, render: (description: AgentConfig['description']) => ( - + {description} ), @@ -239,9 +189,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { }), actions: [ { - render: (config: AgentConfig) => ( - sendRequest()} /> - ), + render: (config: AgentConfig) => , }, ], }, @@ -253,7 +201,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { } return cols; - }, [getHref, isFleetEnabled, sendRequest]); + }, [getHref, isFleetEnabled]); const createAgentConfigButton = useMemo( () => ( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx index b87ae4c4561ff..cdc4f1c63a11d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx @@ -5,12 +5,11 @@ */ import React, { memo } from 'react'; - -import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { useKibanaLink } from '../../../../hooks/use_kibana_link'; +import { FormattedMessage } from '@kbn/i18n/react'; import { DataStream } from '../../../../types'; -import { TableRowActionsNested } from '../../../../components/table_row_actions_nested'; +import { useKibanaLink } from '../../../../hooks'; +import { ContextMenuActions } from '../../../../components'; export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastream }) => { const { dashboards } = datastream; @@ -78,5 +77,5 @@ export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastre }); } - return ; + return ; }); 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 34a7ad8eb1efc..27e17f6b3df61 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,81 +3,70 @@ * 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, useCallback } from 'react'; -import { EuiButton, EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; +import React, { memo, useState } from 'react'; +import { EuiPortal, EuiContextMenuItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { Agent } from '../../../../types'; import { useCapabilities } from '../../../../hooks'; -import { useAgentRefresh } from '../hooks'; +import { ContextMenuActions } from '../../../../components'; import { AgentUnenrollProvider, AgentReassignConfigFlyout } from '../../components'; +import { useAgentRefresh } from '../hooks'; export const AgentDetailsActionMenu: React.FunctionComponent<{ agent: Agent; }> = memo(({ agent }) => { const hasWriteCapabilites = useCapabilities().write; const refreshAgent = useAgentRefresh(); - const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsActionsPopoverOpen(false), [ - setIsActionsPopoverOpen, - ]); - const handleToggleMenu = useCallback(() => setIsActionsPopoverOpen(!isActionsPopoverOpen), [ - isActionsPopoverOpen, - ]); const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(false); return ( <> {isReassignFlyoutOpen && ( - setIsReassignFlyoutOpen(false)} /> + + setIsReassignFlyoutOpen(false)} /> + )} - + - - } - isOpen={isActionsPopoverOpen} - closePopover={handleCloseMenu} - > - { - handleCloseMenu(); - setIsReassignFlyoutOpen(true); - }} - key="reassignConfig" - > - - , - - {(unenrollAgentsPrompt) => ( - { - unenrollAgentsPrompt([agent.id], 1, refreshAgent); - }} - > - - - )} - , - ]} - /> - + ), + }} + items={[ + { + setIsReassignFlyoutOpen(true); + }} + key="reassignConfig" + > + + , + + {(unenrollAgentsPrompt) => ( + { + unenrollAgentsPrompt([agent.id], 1, refreshAgent); + }} + > + + + )} + , + ]} + /> ); }); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx index d5b8b393e7ed9..281a8d3a9745c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.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, { useState, useCallback } from 'react'; +import React, { useState } from 'react'; import { EuiBasicTable, EuiButton, @@ -17,10 +17,9 @@ import { EuiPopover, EuiSpacer, EuiText, - EuiButtonIcon, - EuiContextMenuPanel, EuiContextMenuItem, EuiIcon, + EuiPortal, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; @@ -36,12 +35,10 @@ import { useLink, useBreadcrumbs, } from '../../../hooks'; -import { AgentReassignConfigFlyout } from '../components'; -import { SearchBar } from '../../../components/search_bar'; -import { AgentHealth } from '../components/agent_health'; -import { AgentUnenrollProvider } from '../components/agent_unenroll_provider'; +import { SearchBar, ContextMenuActions } from '../../../components'; import { AgentStatusKueryHelper } from '../../../services'; import { AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; +import { AgentReassignConfigFlyout, AgentHealth, AgentUnenrollProvider } from '../components'; const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ overflow: 'hidden', @@ -76,73 +73,53 @@ const RowActions = React.memo<{ agent: Agent; onReassignClick: () => void; refre ({ agent, refresh, onReassignClick }) => { const { getHref } = useLink(); const hasWriteCapabilites = useCapabilities().write; - const [isOpen, setIsOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); - const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); return ( - - } - isOpen={isOpen} - closePopover={handleCloseMenu} - > - - - , - { - handleCloseMenu(); - onReassignClick(); - }} - key="reassignConfig" - > - - , + + + , + { + onReassignClick(); + }} + key="reassignConfig" + > + + , - - {(unenrollAgentsPrompt) => ( - { - unenrollAgentsPrompt([agent.id], 1, () => { - refresh(); - }); - }} - > - - - )} - , - ]} - /> - + + {(unenrollAgentsPrompt) => ( + { + unenrollAgentsPrompt([agent.id], 1, () => { + refresh(); + }); + }} + > + + + )} + , + ]} + /> ); } ); @@ -387,13 +364,15 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { /> ) : null} {agentToReassign && ( - { - setAgentToReassignId(undefined); - agentsRequest.sendRequest(); - }} - /> + + { + setAgentToReassignId(undefined); + agentsRequest.sendRequest(); + }} + /> + )} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts index 085bad2d18375..6dbc8d67caaee 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts @@ -20,5 +20,6 @@ export { appRoutesService, packageToConfigDatasourceInputs, storedDatasourceToAgentDatasource, + configToYaml, AgentStatusKueryHelper, } from '../../../../common'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts index 0a26a16d35cfd..05a97fd2e2a3c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -26,6 +26,7 @@ export { GetAgentConfigsResponse, GetAgentConfigsResponseItem, GetOneAgentConfigResponse, + GetFullAgentConfigResponse, CreateAgentConfigRequest, CreateAgentConfigResponse, UpdateAgentConfigRequest, diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts index f74f898b2baf9..afc146cf90447 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { TypeOf } from '@kbn/config-schema'; -import { RequestHandler } from 'src/core/server'; +import { RequestHandler, ResponseHeaders } from 'src/core/server'; import bluebird from 'bluebird'; +import { configToYaml } from '../../../common/services'; import { appContextService, agentConfigService, datasourceService } from '../../services'; import { listAgents } from '../../services/agents'; import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; @@ -229,3 +230,37 @@ export const getFullAgentConfig: RequestHandler> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + const { + params: { agentConfigId }, + } = request; + + try { + const fullAgentConfig = await agentConfigService.getFullConfig(soClient, agentConfigId); + if (fullAgentConfig) { + const body = configToYaml(fullAgentConfig); + const headers: ResponseHeaders = { + 'content-type': 'text/x-yaml', + 'content-disposition': `attachment; filename="elastic-agent-config-${fullAgentConfig.id}.yml"`, + }; + return response.ok({ + body, + headers, + }); + } else { + return response.customError({ + statusCode: 404, + body: { message: 'Agent config not found' }, + }); + } + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts index e630f3c959590..4f6cfb436b93b 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts @@ -20,6 +20,7 @@ import { updateAgentConfigHandler, deleteAgentConfigsHandler, getFullAgentConfig, + downloadFullAgentConfig, } from './handlers'; export const registerRoutes = (router: IRouter) => { @@ -82,4 +83,14 @@ export const registerRoutes = (router: IRouter) => { }, getFullAgentConfig ); + + // Download one full agent config + router.get( + { + path: AGENT_CONFIG_API_ROUTES.FULL_INFO_DOWNLOAD_PATTERN, + validate: GetFullAgentConfigRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-read`] }, + }, + downloadFullAgentConfig + ); }; diff --git a/x-pack/plugins/ingest_manager/server/services/index.ts b/x-pack/plugins/ingest_manager/server/services/index.ts index 483661b9de915..1a0fb262eeb7f 100644 --- a/x-pack/plugins/ingest_manager/server/services/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/index.ts @@ -5,7 +5,7 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { AgentStatus } from '../../common/types/models'; +import { AgentStatus } from '../types'; import * as settingsService from './settings'; export { ESIndexPatternSavedObjectService } from './es_index_pattern'; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts index ab97ddc0ba723..123a413bb8442 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts @@ -39,4 +39,7 @@ export const GetFullAgentConfigRequestSchema = { params: schema.object({ agentConfigId: schema.string(), }), + query: schema.object({ + download: schema.maybe(schema.boolean()), + }), }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0557663e99696..18186f9a1bfcd 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8121,11 +8121,9 @@ "xpack.ingestManager.agentConfigForm.systemMonitoringText": "システムメトリックを収集", "xpack.ingestManager.agentConfigForm.systemMonitoringTooltipText": "このオプションを有効にすると、システムメトリックと情報を収集するデータソースで構成をブートストラップできます。", "xpack.ingestManager.agentConfigList.actionsColumnTitle": "アクション", - "xpack.ingestManager.agentConfigList.actionsMenuText": "開く", "xpack.ingestManager.agentConfigList.addButton": "エージェント構成を作成", "xpack.ingestManager.agentConfigList.agentsColumnTitle": "エージェント", "xpack.ingestManager.agentConfigList.clearFiltersLinkText": "フィルターを消去", - "xpack.ingestManager.agentConfigList.createDatasourceActionText": "データソースを作成", "xpack.ingestManager.agentConfigList.datasourcesCountColumnTitle": "データソース", "xpack.ingestManager.agentConfigList.descriptionColumnTitle": "説明", "xpack.ingestManager.agentConfigList.loadingAgentConfigsMessage": "エージェント構成を読み込み中...", @@ -8137,7 +8135,6 @@ "xpack.ingestManager.agentConfigList.reloadAgentConfigsButtonText": "再読み込み", "xpack.ingestManager.agentConfigList.revisionNumber": "rev. {revNumber}", "xpack.ingestManager.agentConfigList.updatedOnColumnTitle": "最終更新日:", - "xpack.ingestManager.agentConfigList.viewConfigActionText": "構成を表示", "xpack.ingestManager.agentDetails.actionsButton": "アクション", "xpack.ingestManager.agentDetails.agentConfigurationLabel": "エージェント構成", "xpack.ingestManager.agentDetails.agentDetailsTitle": "エージェント'{id}'", @@ -8198,7 +8195,6 @@ "xpack.ingestManager.agentHealth.onlineStatusText": "オンライン", "xpack.ingestManager.agentHealth.warningStatusText": "エラー", "xpack.ingestManager.agentList.actionsColumnTitle": "アクション", - "xpack.ingestManager.agentList.actionsMenuText": "開く", "xpack.ingestManager.agentList.addButton": "新しいエージェントを登録", "xpack.ingestManager.agentList.clearFiltersLinkText": "フィルターを消去", "xpack.ingestManager.agentList.configColumnTitle": "構成", @@ -8266,7 +8262,6 @@ "xpack.ingestManager.configDetails.datasourcesTable.streamsCountColumnTitle": "ストリーム", "xpack.ingestManager.configDetails.subTabs.datasourcesTabText": "データソース", "xpack.ingestManager.configDetails.subTabs.settingsTabText": "設定", - "xpack.ingestManager.configDetails.subTabs.yamlTabText": "YAML", "xpack.ingestManager.configDetails.summary.datasources": "データソース", "xpack.ingestManager.configDetails.summary.lastUpdated": "最終更新日:", "xpack.ingestManager.configDetails.summary.revision": "リビジョン", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4f62da260d43f..195309f640a0a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8125,11 +8125,9 @@ "xpack.ingestManager.agentConfigForm.systemMonitoringText": "收集系统指标", "xpack.ingestManager.agentConfigForm.systemMonitoringTooltipText": "启用此选项可使用收集系统指标和信息的数据源启动您的配置。", "xpack.ingestManager.agentConfigList.actionsColumnTitle": "操作", - "xpack.ingestManager.agentConfigList.actionsMenuText": "打开", "xpack.ingestManager.agentConfigList.addButton": "创建代理配置", "xpack.ingestManager.agentConfigList.agentsColumnTitle": "代理", "xpack.ingestManager.agentConfigList.clearFiltersLinkText": "清除筛选", - "xpack.ingestManager.agentConfigList.createDatasourceActionText": "创建数据源", "xpack.ingestManager.agentConfigList.datasourcesCountColumnTitle": "数据源", "xpack.ingestManager.agentConfigList.descriptionColumnTitle": "描述", "xpack.ingestManager.agentConfigList.loadingAgentConfigsMessage": "正在加载代理配置……", @@ -8141,7 +8139,6 @@ "xpack.ingestManager.agentConfigList.reloadAgentConfigsButtonText": "重新加载", "xpack.ingestManager.agentConfigList.revisionNumber": "修订 {revNumber}", "xpack.ingestManager.agentConfigList.updatedOnColumnTitle": "最后更新时间", - "xpack.ingestManager.agentConfigList.viewConfigActionText": "查看配置", "xpack.ingestManager.agentDetails.actionsButton": "操作", "xpack.ingestManager.agentDetails.agentConfigurationLabel": "代理配置", "xpack.ingestManager.agentDetails.agentDetailsTitle": "代理“{id}”", @@ -8202,7 +8199,6 @@ "xpack.ingestManager.agentHealth.onlineStatusText": "联机", "xpack.ingestManager.agentHealth.warningStatusText": "错误", "xpack.ingestManager.agentList.actionsColumnTitle": "操作", - "xpack.ingestManager.agentList.actionsMenuText": "打开", "xpack.ingestManager.agentList.addButton": "注册新代理", "xpack.ingestManager.agentList.clearFiltersLinkText": "清除筛选", "xpack.ingestManager.agentList.configColumnTitle": "配置", @@ -8270,7 +8266,6 @@ "xpack.ingestManager.configDetails.datasourcesTable.streamsCountColumnTitle": "流计数", "xpack.ingestManager.configDetails.subTabs.datasourcesTabText": "数据源", "xpack.ingestManager.configDetails.subTabs.settingsTabText": "设置", - "xpack.ingestManager.configDetails.subTabs.yamlTabText": "YAML", "xpack.ingestManager.configDetails.summary.datasources": "数据源", "xpack.ingestManager.configDetails.summary.lastUpdated": "最后更新时间", "xpack.ingestManager.configDetails.summary.revision": "修订",