From d73b57a671fba577143eae6db1af204be27d414d Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Fri, 28 Oct 2022 10:28:58 -0400 Subject: [PATCH] [Fleet] Add fleet server host UI (#142894) --- .../plugins/fleet/common/services/routes.ts | 12 + .../fleet/common/types/models/agent_policy.ts | 1 + .../fleet/common/types/models/settings.ts | 2 +- .../types/rest_spec/fleet_server_hosts.ts | 20 ++ .../fleet/cypress/e2e/a11y/home_page.cy.ts | 9 +- .../fleet/cypress/e2e/fleet_settings.cy.ts | 44 +++- .../fleet/cypress/e2e/fleet_startup.cy.ts | 7 +- x-pack/plugins/fleet/cypress/screens/fleet.ts | 17 +- .../fleet/cypress/tasks/fleet_server.ts | 8 +- .../advanced_tab.tsx | 2 +- .../components/fleet_server_host_combobox.tsx | 12 +- .../hooks/use_fleet_server_host.ts | 151 ++++++----- .../hooks/use_quick_start_form.ts | 49 ++-- .../quick_start_tab.tsx | 22 +- .../steps/add_fleet_server_host.tsx | 127 +++++---- .../steps/get_started.tsx | 99 ++++--- .../install_agent/install_agent_managed.tsx | 8 +- .../multi_page_layout/index.tsx | 15 +- .../multi_page_layout/types.ts | 3 +- .../fleet/sections/agents/index.test.tsx | 8 - .../index.stories.tsx | 12 +- .../fleet_server_hosts_flyout/index.tsx | 137 +++++++--- .../use_fleet_server_host_form.test.tsx | 52 ++-- .../use_fleet_server_host_form.tsx | 145 ++++++----- .../fleet_server_hosts_table/index.tsx | 156 +++++++++++ .../components/multi_row_input/index.tsx | 1 + .../fleet_server_hosts_section.tsx | 75 ++++++ .../components/settings_page/index.tsx | 15 +- .../settings_section.stories.tsx | 44 ---- .../settings_page/settings_section.tsx | 110 -------- .../fleet/sections/settings/hooks/index.tsx | 6 + .../hooks/use_delete_fleet_server_host.tsx | 70 +++++ .../fleet/sections/settings/index.tsx | 48 +++- .../agent_enrollment_flyout.test.mocks.ts | 10 + .../agent_enrollment_flyout.test.tsx | 18 +- .../agent_enrollment_flyout/index.tsx | 13 +- .../agent_enrollment_flyout/instructions.tsx | 12 +- .../steps/compute_steps.tsx | 15 +- .../agent_enrollment_flyout/types.ts | 4 +- .../fleet/public/constants/page_paths.ts | 16 +- .../hooks/use_request/fleet_server_hosts.ts | 45 ++++ .../fleet/public/hooks/use_request/index.ts | 1 + x-pack/plugins/fleet/public/types/index.ts | 1 + .../agent_policies/full_agent_policy.test.ts | 7 +- .../agent_policies/full_agent_policy.ts | 31 +-- .../server/services/agent_policy.test.ts | 45 ++++ .../server/services/fleet_server_host.test.ts | 47 ---- .../server/services/fleet_server_host.ts | 26 ++ .../fleet_server_host.test.ts | 52 +++- .../preconfiguration/fleet_server_host.ts | 37 ++- .../fleet/server/services/settings.test.ts | 245 +----------------- .../plugins/fleet/server/services/settings.ts | 47 +--- x-pack/plugins/fleet/server/services/setup.ts | 5 +- .../translations/translations/fr-FR.json | 11 - .../translations/translations/ja-JP.json | 11 - .../translations/translations/zh-CN.json | 11 - .../es_archives/fleet/agents/data.json | 14 + 57 files changed, 1249 insertions(+), 962 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_table/index.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/fleet_server_hosts_section.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/settings_section.stories.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/settings_section.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/settings/hooks/index.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/settings/hooks/use_delete_fleet_server_host.tsx create mode 100644 x-pack/plugins/fleet/public/hooks/use_request/fleet_server_hosts.ts diff --git a/x-pack/plugins/fleet/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts index c2f76758c3d7b..4c8f053b56cf9 100644 --- a/x-pack/plugins/fleet/common/services/routes.ts +++ b/x-pack/plugins/fleet/common/services/routes.ts @@ -21,6 +21,7 @@ import { K8S_API_ROUTES, PRECONFIGURATION_API_ROUTES, DOWNLOAD_SOURCE_API_ROUTES, + FLEET_SERVER_HOST_API_ROUTES, } from '../constants'; export const epmRouteService = { @@ -218,6 +219,17 @@ export const outputRoutesService = { getCreateLogstashApiKeyPath: () => OUTPUT_API_ROUTES.LOGSTASH_API_KEY_PATTERN, }; +export const fleetServerHostsRoutesService = { + getInfoPath: (itemId: string) => + FLEET_SERVER_HOST_API_ROUTES.INFO_PATTERN.replace('{itemId}', itemId), + getUpdatePath: (itemId: string) => + FLEET_SERVER_HOST_API_ROUTES.UPDATE_PATTERN.replace('{itemId}', itemId), + getListPath: () => FLEET_SERVER_HOST_API_ROUTES.LIST_PATTERN, + getDeletePath: (itemId: string) => + FLEET_SERVER_HOST_API_ROUTES.DELETE_PATTERN.replace('{itemId}', itemId), + getCreatePath: () => FLEET_SERVER_HOST_API_ROUTES.CREATE_PATTERN, +}; + export const settingsRoutesService = { getInfoPath: () => SETTINGS_API_ROUTES.INFO_PATTERN, getUpdatePath: () => SETTINGS_API_ROUTES.UPDATE_PATTERN, diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts index ea22f73a2e5f9..8389cb07da15c 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -29,6 +29,7 @@ export interface NewAgentPolicy { data_output_id?: string | null; monitoring_output_id?: string | null; download_source_id?: string | null; + fleet_server_host_id?: string | null; schema_version?: string; } diff --git a/x-pack/plugins/fleet/common/types/models/settings.ts b/x-pack/plugins/fleet/common/types/models/settings.ts index 17bbe7a73c1da..5a33fea910446 100644 --- a/x-pack/plugins/fleet/common/types/models/settings.ts +++ b/x-pack/plugins/fleet/common/types/models/settings.ts @@ -9,7 +9,7 @@ import type { SavedObjectAttributes } from '@kbn/core/public'; export interface BaseSettings { has_seen_add_data_notice?: boolean; - fleet_server_hosts: string[]; + fleet_server_hosts?: string[]; } export interface Settings extends BaseSettings { diff --git a/x-pack/plugins/fleet/common/types/rest_spec/fleet_server_hosts.ts b/x-pack/plugins/fleet/common/types/rest_spec/fleet_server_hosts.ts index bf8be3cb38407..178e7679947a8 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/fleet_server_hosts.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/fleet_server_hosts.ts @@ -10,3 +10,23 @@ import type { FleetServerHost } from '../models'; import type { ListResult } from './common'; export type GetFleetServerHostsResponse = ListResult; + +export interface PutFleetServerHostsRequest { + params: { + itemId: string; + }; + body: { + name?: string; + host_urls?: string[]; + is_default?: boolean; + }; +} + +export interface PostFleetServerHostsRequest { + body: { + id?: string; + name?: string; + host_urls?: string[]; + is_default?: boolean; + }; +} diff --git a/x-pack/plugins/fleet/cypress/e2e/a11y/home_page.cy.ts b/x-pack/plugins/fleet/cypress/e2e/a11y/home_page.cy.ts index b76942ec9a456..6d1f714bda0d5 100644 --- a/x-pack/plugins/fleet/cypress/e2e/a11y/home_page.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/a11y/home_page.cy.ts @@ -24,7 +24,7 @@ import { DATA_STREAMS_TAB, SETTINGS_TAB, SETTINGS_FLEET_SERVER_HOST_HEADING, - FLEET_SERVER_HOST_INPUT, + FLEET_SERVER_SETUP, } from '../../screens/fleet'; import { AGENT_POLICY_NAME_LINK } from '../../screens/integrations'; import { cleanupAgentPolicies, unenrollAgent } from '../../tasks/cleanup'; @@ -42,8 +42,9 @@ describe('Home page', () => { checkA11y({ skipFailures: false }); }); it('Install Fleet Server', () => { - cy.getBySel(FLEET_SERVER_HOST_INPUT, { timeout: 15000 }).should('be.visible'); - cy.getBySel(FLEET_SERVER_HOST_INPUT).getBySel('comboBoxSearchInput').type(fleetServerHost); + cy.getBySel(FLEET_SERVER_SETUP.NAME_INPUT).type('Host edited'); + cy.get('[placeholder="Specify host URL"', { timeout: 15000 }).should('be.visible'); + cy.get('[placeholder="Specify host URL"').type(fleetServerHost); cy.getBySel(GENERATE_FLEET_SERVER_POLICY_BUTTON).click(); cy.getBySel(PLATFORM_TYPE_LINUX_BUTTON, { timeout: 15000 }).should('be.visible'); checkA11y({ skipFailures: false }); @@ -58,6 +59,8 @@ describe('Home page', () => { checkA11y({ skipFailures: false }); }); it('Add your fleet sever host', () => { + cy.getBySel(FLEET_SERVER_SETUP.NAME_INPUT).type('New host'); + cy.get('[placeholder="Specify host URL"').type('https://localhost:8220'); cy.getBySel(ADVANCED_FLEET_SERVER_ADD_HOST_BUTTON).click(); checkA11y({ skipFailures: false }); }); diff --git a/x-pack/plugins/fleet/cypress/e2e/fleet_settings.cy.ts b/x-pack/plugins/fleet/cypress/e2e/fleet_settings.cy.ts index a1c4eef06bdb5..8e213532ce7b4 100644 --- a/x-pack/plugins/fleet/cypress/e2e/fleet_settings.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/fleet_settings.cy.ts @@ -6,12 +6,27 @@ */ import { TOAST_CLOSE_BTN, CONFIRM_MODAL } from '../screens/navigation'; -import { SETTINGS_SAVE_BTN, SETTINGS_OUTPUTS } from '../screens/fleet'; +import { + SETTINGS_SAVE_BTN, + SETTINGS_OUTPUTS, + SETTINGS_FLEET_SERVER_HOSTS, + FLEET_SERVER_HOST_FLYOUT, +} from '../screens/fleet'; describe('Edit settings', () => { beforeEach(() => { - cy.intercept('/api/fleet/settings', { - item: { id: 'fleet-default-settings', fleet_server_hosts: [] }, + cy.intercept('/api/fleet/fleet_server_hosts', { + items: [ + { + id: 'fleet-default-settings', + name: 'Host', + host_urls: ['https://localhost:8220'], + is_default: true, + }, + ], + page: 1, + perPage: 10000, + total: 0, }); cy.intercept('/api/fleet/outputs', { items: [ @@ -29,22 +44,23 @@ describe('Edit settings', () => { cy.getBySel(TOAST_CLOSE_BTN).click(); }); - it('should update Fleet server hosts', () => { - cy.getBySel(SETTINGS_OUTPUTS.EDIT_HOSTS_BTN).click(); - cy.get('[placeholder="Specify host URL"').type('https://localhost:8220'); + it('should allow to update Fleet server hosts', () => { + cy.getBySel(SETTINGS_FLEET_SERVER_HOSTS.ADD_BUTTON).click(); + cy.getBySel(FLEET_SERVER_HOST_FLYOUT.NAME_INPUT).type('Host edited'); + cy.getBySel(FLEET_SERVER_HOST_FLYOUT.DEFAULT_SWITCH).click(); + cy.get('[placeholder="Specify host URL"').type('https://localhost:8221'); - cy.intercept('/api/fleet/settings', { - item: { id: 'fleet-default-settings', fleet_server_hosts: ['https://localhost:8220'] }, - }); - cy.intercept('PUT', '/api/fleet/settings', { - fleet_server_hosts: ['https://localhost:8220'], - }).as('updateSettings'); + cy.intercept('POST', '/api/fleet/fleet_server_hosts', { + name: 'Host edited', + host_urls: ['https://localhost:8221'], + is_default: true, + }).as('updateFleetServerHosts'); cy.getBySel(SETTINGS_SAVE_BTN).click(); cy.getBySel(CONFIRM_MODAL.CONFIRM_BUTTON).click(); - cy.wait('@updateSettings').then((interception) => { - expect(interception.request.body.fleet_server_hosts[0]).to.equal('https://localhost:8220'); + cy.wait('@updateFleetServerHosts').then((interception) => { + expect(interception.request.body.host_urls[0]).to.equal('https://localhost:8221'); }); }); diff --git a/x-pack/plugins/fleet/cypress/e2e/fleet_startup.cy.ts b/x-pack/plugins/fleet/cypress/e2e/fleet_startup.cy.ts index befa2074ac865..d79793d10d6bb 100644 --- a/x-pack/plugins/fleet/cypress/e2e/fleet_startup.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/fleet_startup.cy.ts @@ -11,9 +11,9 @@ import { AGENT_FLYOUT, CREATE_FLEET_SERVER_POLICY_BTN, AGENT_POLICY_CREATE_STATUS_CALLOUT, - FLEET_SERVER_HOST_INPUT, ADVANCED_FLEET_SERVER_ADD_HOST_BUTTON, ADVANCED_FLEET_SERVER_GENERATE_SERVICE_TOKEN_BUTTON, + FLEET_SERVER_SETUP, } from '../screens/fleet'; import { cleanupAgentPolicies, unenrollAgent } from '../tasks/cleanup'; import { verifyPolicy, verifyAgentPackage, navigateToTab } from '../tasks/fleet'; @@ -98,9 +98,8 @@ describe('Fleet startup', () => { cy.getBySel(AGENT_FLYOUT.POLICY_DROPDOWN); // verify fleet server enroll command contains created policy id - cy.getBySel(FLEET_SERVER_HOST_INPUT) - .getBySel('comboBoxSearchInput') - .type('https://localhost:8220'); + cy.getBySel(FLEET_SERVER_SETUP.NAME_INPUT).type('New host'); + cy.get('[placeholder="Specify host URL"').type('https://localhost:8220'); cy.getBySel(ADVANCED_FLEET_SERVER_ADD_HOST_BUTTON).click(); cy.getBySel(ADVANCED_FLEET_SERVER_GENERATE_SERVICE_TOKEN_BUTTON).click(); diff --git a/x-pack/plugins/fleet/cypress/screens/fleet.ts b/x-pack/plugins/fleet/cypress/screens/fleet.ts index a9df1dc4d8ef1..7bd8c6293e97d 100644 --- a/x-pack/plugins/fleet/cypress/screens/fleet.ts +++ b/x-pack/plugins/fleet/cypress/screens/fleet.ts @@ -48,7 +48,7 @@ export const SETTINGS_SAVE_BTN = 'saveApplySettingsBtn'; export const AGENT_POLICY_SYSTEM_MONITORING_CHECKBOX = 'agentPolicyFormSystemMonitoringCheckbox'; export const INSTALL_INTEGRATIONS_ADVANCE_OPTIONS_BTN = 'AgentPolicyAdvancedOptions.AccordionBtn'; export const AGENT_POLICY_CREATE_STATUS_CALLOUT = 'agentPolicyCreateStatusCallOut'; -export const FLEET_SERVER_HOST_INPUT = 'fleetServerHostInput'; + export const EXISTING_HOSTS_TAB = 'existingHostsTab'; export const NEW_HOSTS_TAB = 'newHostsTab'; @@ -96,11 +96,14 @@ export const AGENT_BINARY_SOURCES_FLYOUT = { export const SETTINGS_OUTPUTS = { EDIT_BTN: 'editOutputBtn', ADD_BTN: 'addOutputBtn', - EDIT_HOSTS_BTN: 'editHostsBtn', NAME_INPUT: 'settingsOutputsFlyout.nameInput', TYPE_INPUT: 'settingsOutputsFlyout.typeInput', }; +export const SETTINGS_FLEET_SERVER_HOSTS = { + ADD_BUTTON: 'settings.fleetServerHosts.addFleetServerHostBtn', +}; + export const AGENT_POLICY_FORM = { DOWNLOAD_SOURCE_SELECT: 'agentPolicyForm.downloadSource.select', }; @@ -114,3 +117,13 @@ export const FLEET_AGENT_LIST_PAGE = { CHECKBOX_SELECT_ALL: 'checkboxSelectAll', BULK_ACTIONS_BUTTON: 'agentBulkActionsButton', }; + +export const FLEET_SERVER_HOST_FLYOUT = { + NAME_INPUT: 'fleetServerHostsFlyout.nameInput', + DEFAULT_SWITCH: 'fleetServerHostsFlyout.isDefaultSwitch', +}; + +export const FLEET_SERVER_SETUP = { + NAME_INPUT: 'fleetServerSetup.nameInput', + HOST_INPUT: 'fleetServerSetup.multiRowInput', +}; diff --git a/x-pack/plugins/fleet/cypress/tasks/fleet_server.ts b/x-pack/plugins/fleet/cypress/tasks/fleet_server.ts index 946ded57e738f..4a75d535c12f2 100644 --- a/x-pack/plugins/fleet/cypress/tasks/fleet_server.ts +++ b/x-pack/plugins/fleet/cypress/tasks/fleet_server.ts @@ -51,11 +51,13 @@ export function setupFleetServer() { export function setFleetServerHost(host = 'https://fleetserver:8220') { cy.request({ - method: 'PUT', - url: '/api/fleet/settings', + method: 'POST', + url: '/api/fleet/fleet_server_hosts', headers: { 'kbn-xsrf': 'xx' }, body: { - fleet_server_hosts: [host], + name: 'Default host', + host_urls: [host], + is_default: true, }, }); } diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx index e13d9b5394dc9..c0d0cec2fb5a2 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx @@ -63,7 +63,7 @@ export const AdvancedTab: React.FunctionComponent = ({ selecte getInstallFleetServerStep({ isFleetServerReady, serviceToken, - fleetServerHost: fleetServerHostForm.fleetServerHost, + fleetServerHost: fleetServerHostForm.fleetServerHost?.host_urls[0], fleetServerPolicyId: fleetServerPolicyId || selectedPolicyId, deploymentMode, disabled: !Boolean(serviceToken), diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/fleet_server_host_combobox.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/fleet_server_host_combobox.tsx index 0508bd9108d3a..ca9e9755496ce 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/fleet_server_host_combobox.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/fleet_server_host_combobox.tsx @@ -6,12 +6,15 @@ */ import React, { useState } from 'react'; import type { EuiComboBoxOptionOption } from '@elastic/eui'; + import { EuiComboBox, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; +import type { FleetServerHost } from '../../../types'; + interface Props { - fleetServerHost: string | undefined; + fleetServerHost: FleetServerHost | undefined; fleetServerHostSettings: string[]; isDisabled: boolean; isInvalid: boolean; @@ -42,7 +45,6 @@ export const FleetServerHostComboBox: React.FunctionComponent = ({ setCreatedOptions([...createdOptions, option]); onFleetServerHostChange(option); }; - return ( fullWidth @@ -57,7 +59,11 @@ export const FleetServerHostComboBox: React.FunctionComponent = ({ values: { searchValuePlaceholder: '{searchValue}' }, } )} - selectedOptions={fleetServerHost ? [{ label: fleetServerHost, value: fleetServerHost }] : []} + selectedOptions={ + fleetServerHost + ? [{ label: fleetServerHost.host_urls[0], value: fleetServerHost.host_urls[0] }] + : [] + } prepend={ Promise; - fleetServerHost?: string; - fleetServerHostSettings: string[]; + saveFleetServerHost: (host: FleetServerHost) => Promise; + fleetServerHost?: FleetServerHost; isFleetServerHostSubmitted: boolean; - setFleetServerHost: React.Dispatch>; + setFleetServerHost: React.Dispatch>; + validate: () => boolean; error?: string; - validateFleetServerHost: () => boolean; + inputs: { + hostUrlsInput: ReturnType; + nameInput: ReturnType; + }; } export const useFleetServerHost = (): FleetServerHostForm => { - const [fleetServerHost, setFleetServerHost] = useState(); + const [fleetServerHost, setFleetServerHost] = useState(); const [isFleetServerHostSubmitted, setIsFleetServerHostSubmitted] = useState(false); - const [error, setError] = useState(); - - const { data: settings } = useGetSettings(); - - useEffect(() => { - const settingsFleetServerHosts = settings?.item.fleet_server_hosts ?? []; - - if (settingsFleetServerHosts.length) { - setFleetServerHost(settingsFleetServerHosts[0]); - } - }, [settings?.item.fleet_server_hosts]); - const validateFleetServerHost = useCallback(() => { - if (!fleetServerHost) { - setError( - i18n.translate('xpack.fleet.fleetServerHost.requiredError', { - defaultMessage: 'Fleet server host is required.', - }) - ); + const isPreconfigured = fleetServerHost?.is_preconfigured ?? false; + const nameInput = useInput(fleetServerHost?.name ?? '', validateName, isPreconfigured); - return false; - } else if (!fleetServerHost.startsWith('https')) { - setError( - i18n.translate('xpack.fleet.fleetServerHost.requiresHttpsError', { - defaultMessage: 'Fleet server host must begin with "https"', - }) - ); + const hostUrlsInput = useComboInput( + 'hostUrls', + fleetServerHost?.host_urls || [], + validateFleetServerHosts, + isPreconfigured + ); + const validate = useCallback( + () => hostUrlsInput.validate() && nameInput.validate(), + [hostUrlsInput, nameInput] + ); - return false; - } else if (!fleetServerHost.match(URL_REGEX)) { - setError( - i18n.translate('xpack.fleet.fleetServerSetup.addFleetServerHostInvalidUrlError', { - defaultMessage: 'Invalid URL', - }) - ); - - return false; - } + const { data } = useGetFleetServerHosts(); - return true; - }, [fleetServerHost]); - - const saveFleetServerHost = useCallback(async () => { - setIsFleetServerHostSubmitted(false); - - if (!validateFleetServerHost()) { - return; + useEffect(() => { + const fleetServerHosts = data?.items ?? []; + const defaultHost = fleetServerHosts.find((item) => item.is_default === true); + + // Get the default host, otherwise the first fleet server found + if (defaultHost) { + setFleetServerHost(defaultHost); + } else { + setFleetServerHost(fleetServerHosts[0]); } - - // If the Fleet Server host provided already exists in settings, don't submit it - if (settings?.item.fleet_server_hosts.includes(fleetServerHost!)) { + }, [data?.items, fleetServerHost]); + + const saveFleetServerHost = useCallback( + async (newFleetServerHost: FleetServerHost) => { + setIsFleetServerHostSubmitted(false); + setFleetServerHost(newFleetServerHost); + + const fleetServerHostExists = data?.items.reduce((acc, curr) => { + const hostsIntersection = intersection(curr.host_urls, newFleetServerHost?.host_urls); + return hostsIntersection.length > 0 || acc; + }, false); + + // If the Fleet Server host provided already exists in settings, don't submit it + if (fleetServerHostExists) { + setIsFleetServerHostSubmitted(true); + return; + } + if (newFleetServerHost) { + const res = await sendPostFleetServerHost({ + name: newFleetServerHost?.name, + host_urls: newFleetServerHost?.host_urls, + is_default: newFleetServerHost?.is_default, + }); + if (res.error) { + throw res.error; + } + } setIsFleetServerHostSubmitted(true); - return; - } - - const res = await sendPutSettings({ - fleet_server_hosts: [fleetServerHost!, ...(settings?.item.fleet_server_hosts || [])], - }); - - if (res.error) { - throw res.error; - } - - setIsFleetServerHostSubmitted(true); - }, [fleetServerHost, settings?.item.fleet_server_hosts, validateFleetServerHost]); + }, + [data?.items] + ); return { saveFleetServerHost, fleetServerHost, - fleetServerHostSettings: settings?.item.fleet_server_hosts ?? [], isFleetServerHostSubmitted, setFleetServerHost, - error, - validateFleetServerHost, + validate, + inputs: { + hostUrlsInput, + nameInput, + }, }; }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts index 84fd39aeec378..f3167b977d312 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts @@ -8,10 +8,13 @@ import { useState, useCallback, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; +import type { useComboInput, useInput } from '../../../hooks'; import { sendCreateAgentPolicy, sendGetOneAgentPolicy, useStartServices } from '../../../hooks'; import type { NewAgentPolicy } from '../../../types'; +import type { FleetServerHost } from '../../../types'; + import { useSelectFleetServerPolicy } from './use_select_fleet_server_policy'; import { useServiceToken } from './use_service_token'; import { useFleetServerHost } from './use_fleet_server_host'; @@ -32,12 +35,14 @@ export interface QuickStartCreateForm { status: QuickStartCreateFormStatus; error?: string; submit: () => void; - fleetServerHost?: string; - fleetServerHostSettings: string[]; + fleetServerHost?: FleetServerHost; isFleetServerHostSubmitted: boolean; - onFleetServerHostChange: (value: string) => void; fleetServerPolicyId?: string; serviceToken?: string; + inputs: { + hostUrlsInput: ReturnType; + nameInput: ReturnType; + }; } /** @@ -52,12 +57,12 @@ export const useQuickStartCreateForm = (): QuickStartCreateForm => { const { fleetServerHost, - fleetServerHostSettings, isFleetServerHostSubmitted, - setFleetServerHost, - validateFleetServerHost, saveFleetServerHost, error: fleetServerError, + setFleetServerHost, + validate, + inputs, } = useFleetServerHost(); // When a validation error is surfaced from the Fleet Server host form, we want to treat it @@ -71,18 +76,20 @@ export const useQuickStartCreateForm = (): QuickStartCreateForm => { const { fleetServerPolicyId, setFleetServerPolicyId } = useSelectFleetServerPolicy(); const { serviceToken, generateServiceToken } = useServiceToken(); - const onFleetServerHostChange = useCallback( - (value: string) => { - setFleetServerHost(value); - }, - [setFleetServerHost] - ); - const submit = useCallback(async () => { try { - if (validateFleetServerHost()) { + if (validate()) { setStatus('loading'); - await saveFleetServerHost(); + + const newFleetServerHost = { + name: inputs.nameInput.value, + host_urls: inputs.hostUrlsInput.value, + is_default: true, + id: 'fleet-server-host', + is_preconfigured: false, + }; + setFleetServerHost(newFleetServerHost); + await saveFleetServerHost(newFleetServerHost); await generateServiceToken(); const existingPolicy = await sendGetOneAgentPolicy( @@ -99,11 +106,9 @@ export const useQuickStartCreateForm = (): QuickStartCreateForm => { withSysMonitoring: true, } ); - setFleetServerPolicyId(createPolicyResponse.data?.item.id); } - setFleetServerHost(fleetServerHost); setStatus('success'); } } catch (err) { @@ -117,11 +122,12 @@ export const useQuickStartCreateForm = (): QuickStartCreateForm => { setError(err.message); } }, [ - validateFleetServerHost, + validate, + inputs.nameInput.value, + inputs.hostUrlsInput.value, + setFleetServerHost, saveFleetServerHost, generateServiceToken, - setFleetServerHost, - fleetServerHost, setFleetServerPolicyId, notifications.toasts, ]); @@ -132,9 +138,8 @@ export const useQuickStartCreateForm = (): QuickStartCreateForm => { submit, fleetServerPolicyId, fleetServerHost, - fleetServerHostSettings, isFleetServerHostSubmitted, - onFleetServerHostChange, serviceToken, + inputs, }; }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/quick_start_tab.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/quick_start_tab.tsx index 758a34113efcd..d6e28a684f302 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/quick_start_tab.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/quick_start_tab.tsx @@ -17,24 +17,32 @@ import { } from './steps'; export const QuickStartTab: React.FunctionComponent = () => { - const quickStartCreateForm = useQuickStartCreateForm(); + const { fleetServerHost, fleetServerPolicyId, serviceToken, status, error, submit, inputs } = + useQuickStartCreateForm(); const { isFleetServerReady } = useWaitForFleetServer(); const steps = [ getGettingStartedStep({ - quickStartCreateForm, + fleetServerHost, + fleetServerPolicyId, + serviceToken, + status, + error, + submit, + isFleetServerHostSubmitted: false, + inputs, }), getInstallFleetServerStep({ isFleetServerReady, - fleetServerHost: quickStartCreateForm.fleetServerHost, - fleetServerPolicyId: quickStartCreateForm.fleetServerPolicyId, - serviceToken: quickStartCreateForm.serviceToken, + fleetServerHost: fleetServerHost?.host_urls[0], + fleetServerPolicyId, + serviceToken, deploymentMode: 'quickstart', - disabled: quickStartCreateForm.status !== 'success', + disabled: status !== 'success', }), getConfirmFleetServerConnectionStep({ isFleetServerReady, - disabled: quickStartCreateForm.status !== 'success', + disabled: status !== 'success', }), ]; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/add_fleet_server_host.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/add_fleet_server_host.tsx index 62b11e3295ecf..b4f4a6d9e0826 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/add_fleet_server_host.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/add_fleet_server_host.tsx @@ -11,20 +11,22 @@ import { EuiButton, EuiCallOut, EuiCode, - EuiFlexGroup, - EuiFlexItem, EuiForm, EuiFormErrorText, EuiLink, EuiSpacer, EuiText, + EuiFormRow, + EuiFieldText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import type { FleetServerHost } from '../../../types'; + import { useStartServices, useLink } from '../../../hooks'; import type { FleetServerHostForm } from '../hooks'; -import { FleetServerHostComboBox } from '../components'; +import { MultiRowInput } from '../../../sections/settings/components/multi_row_input'; export const getAddFleetServerHostStep = ({ fleetServerHostForm, @@ -49,28 +51,29 @@ export const AddFleetServerHostStepContent = ({ }: { fleetServerHostForm: FleetServerHostForm; }) => { - const { - fleetServerHost, - fleetServerHostSettings, - setFleetServerHost, - validateFleetServerHost, - saveFleetServerHost, - error, - } = fleetServerHostForm; + const { setFleetServerHost, saveFleetServerHost, error, validate, inputs } = fleetServerHostForm; const [isLoading, setIsLoading] = useState(false); - const [submittedFleetServerHost, setSubmittedFleetServerHost] = useState(); + const [submittedFleetServerHost, setSubmittedFleetServerHost] = useState(); const { notifications } = useStartServices(); const { getHref } = useLink(); const onSubmit = useCallback(async () => { try { - setSubmittedFleetServerHost(''); + setSubmittedFleetServerHost(undefined); setIsLoading(true); - if (validateFleetServerHost()) { - await saveFleetServerHost(); - setSubmittedFleetServerHost(fleetServerHost); + const newFleetServerHost = { + name: inputs.nameInput.value, + host_urls: inputs.hostUrlsInput.value, + is_default: true, + id: 'fleet-server-host', + is_preconfigured: false, + }; + setFleetServerHost(newFleetServerHost); + if (validate()) { + await saveFleetServerHost(newFleetServerHost); + setSubmittedFleetServerHost(newFleetServerHost); } } catch (err) { notifications.toasts.addError(err, { @@ -81,18 +84,14 @@ export const AddFleetServerHostStepContent = ({ } finally { setIsLoading(false); } - }, [validateFleetServerHost, saveFleetServerHost, fleetServerHost, notifications.toasts]); - - const onChange = useCallback( - (host: string) => { - setFleetServerHost(host); - - if (error) { - validateFleetServerHost(); - } - }, - [error, setFleetServerHost, validateFleetServerHost] - ); + }, [ + inputs.nameInput.value, + inputs.hostUrlsInput.value, + setFleetServerHost, + validate, + saveFleetServerHost, + notifications.toasts, + ]); return ( @@ -104,34 +103,52 @@ export const AddFleetServerHostStepContent = ({ /> - - - + } + {...inputs.nameInput.formRowProps} + > + + + + } + > + <> + {error && {error}} - - - - - - - + + + + + + {submittedFleetServerHost && ( <> @@ -150,7 +167,7 @@ export const AddFleetServerHostStepContent = ({ id="xpack.fleet.fleetServerSetup.addFleetServerHostSuccessText" defaultMessage="Added {host}. You can edit your Fleet Server hosts in {fleetSettingsLink}." values={{ - host: submittedFleetServerHost, + host: submittedFleetServerHost.host_urls[0], fleetSettingsLink: ( , + status: props.status === 'success' ? 'complete' : 'current', + children: , }; } -const GettingStartedStepContent: React.FunctionComponent<{ - quickStartCreateForm: QuickStartCreateForm; -}> = ({ quickStartCreateForm }) => { +const GettingStartedStepContent: React.FunctionComponent = ({ + fleetServerHost, + status, + error, + inputs, + submit, +}) => { const { getHref } = useLink(); - const { fleetServerHost, fleetServerHostSettings, onFleetServerHostChange } = - quickStartCreateForm; - - if (quickStartCreateForm.status === 'success') { + if (status === 'success') { return ( {fleetServerHost}, + hostUrl: {fleetServerHost?.host_urls[0]}, fleetSettingsLink: ( 8220 }} /> @@ -96,32 +93,52 @@ const GettingStartedStepContent: React.FunctionComponent<{ - - - - + - - {quickStartCreateForm.status === 'error' && ( - {quickStartCreateForm.error} - )} - - + } + {...inputs.nameInput.formRowProps} + > + + + + } + > + <> + + {status === 'error' && {error}} + + setIsManaged, agentPolicy, enrollmentAPIKey, - settings, + fleetServerHosts, enrolledAgentIds, } = props; @@ -40,10 +40,6 @@ export const InstallElasticAgentManagedPageStep: React.FC const [commandCopied, setCommandCopied] = useState(false); - const fleetServerHosts = useMemo(() => { - return settings?.fleet_server_hosts || []; - }, [settings]); - if (!enrollmentAPIKey) { return ( packageInfoData?.item, [packageInfoData]); - const settings = useMemo(() => settingsData?.item, [settingsData]); const integrationInfo = useMemo(() => { if (!integration) return; @@ -95,6 +92,10 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({ setOnSplash(false); }; + const fleetServerHostsRequest = useGetFleetServerHosts(); + const fleetServerHosts = + fleetServerHostsRequest.data?.items?.filter((f) => true)?.[0]?.host_urls ?? []; + const cancelUrl = getHref('add_integration_to_policy', { pkgkey, useMultiPageLayout: false, @@ -105,7 +106,9 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({ if (onSplash || !packageInfo) { return ( { }); const mockedUsedFleetStatus = useFleetStatus as jest.MockedFunction; -const mockedUseGetSettings = useGetSettings as jest.MockedFunction; const mockedUseAuthz = useAuthz as jest.MockedFunction; function renderAgentsApp() { @@ -48,12 +46,6 @@ function renderAgentsApp() { } describe('AgentApp', () => { beforeEach(() => { - mockedUseGetSettings.mockReturnValue({ - isLoading: false, - data: { - item: {}, - }, - } as any); mockedUseAuthz.mockReturnValue({ fleet: { all: true, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/index.stories.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/index.stories.tsx index c4ff6917867ca..236c60f2d0c40 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/index.stories.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/index.stories.tsx @@ -21,14 +21,18 @@ interface Args { const args: Args = { width: 1200, }; +const fleetServerHost = { + id: 'id1', + name: 'fleet server 1', + host_urls: ['https://host1.fr:8220', 'https://host2-with-a-longer-name.fr:8220'], + is_default: false, + is_preconfigured: false, +}; export const FleetServerHostsFlyout = ({ width }: Args) => { return (
- {}} - fleetServerHosts={['https://host1.fr:8220', 'https://host2-with-a-longer-name.fr:8220']} - /> + {}} fleetServerHost={fleetServerHost} />
); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/index.tsx index 57c9ded6609b5..b64876d3b2524 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/index.tsx @@ -21,71 +21,132 @@ import { EuiButtonEmpty, EuiButton, EuiSpacer, + EuiForm, + EuiFormRow, + EuiFieldText, + EuiSwitch, } from '@elastic/eui'; import { MultiRowInput } from '../multi_row_input'; import { useStartServices } from '../../../../hooks'; import { FLYOUT_MAX_WIDTH } from '../../constants'; +import type { FleetServerHost } from '../../../../types'; import { useFleetServerHostsForm } from './use_fleet_server_host_form'; export interface FleetServerHostsFlyoutProps { onClose: () => void; - fleetServerHosts: string[]; + fleetServerHost?: FleetServerHost; } export const FleetServerHostsFlyout: React.FunctionComponent = ({ onClose, - fleetServerHosts, + fleetServerHost, }) => { const { docLinks } = useStartServices(); - const form = useFleetServerHostsForm(fleetServerHosts, onClose); + const form = useFleetServerHostsForm(fleetServerHost, onClose); + const { inputs } = form; return ( -

- +

+ {fleetServerHost ? ( + + ) : ( + + )}

- - - - - ), - }} - /> - - - + } - )} - /> + {...inputs.nameInput.formRowProps} + > + + + + } + > + <> + + + + + ), + }} + /> + + + + + + + + } + /> + + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx index aeb49928f3d1e..8df533f0206b7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx @@ -23,16 +23,16 @@ jest.mock('../../hooks/use_confirm_modal', () => ({ describe('useFleetServerHostsForm', () => { it('should not allow to submit an invalid form', async () => { const testRenderer = createFleetTestRendererMock(); - const onSucess = jest.fn(); - const { result } = testRenderer.renderHook(() => useFleetServerHostsForm([], onSucess)); + const onSuccess = jest.fn(); + const { result } = testRenderer.renderHook(() => useFleetServerHostsForm(undefined, onSuccess)); act(() => - result.current.fleetServerHostsInput.props.onChange(['https://test.fr', 'https://test.fr']) + result.current.inputs.hostUrlsInput.props.onChange(['https://test.fr', 'https://test.fr']) ); await act(() => result.current.submit()); - expect(result.current.fleetServerHostsInput.props.errors).toMatchInlineSnapshot(` + expect(result.current.inputs.hostUrlsInput.props.errors).toMatchInlineSnapshot(` Array [ Object { "index": 0, @@ -44,40 +44,62 @@ describe('useFleetServerHostsForm', () => { }, ] `); - expect(onSucess).not.toBeCalled(); + expect(onSuccess).not.toBeCalled(); expect(result.current.isDisabled).toBeTruthy(); }); it('should submit a valid form', async () => { const testRenderer = createFleetTestRendererMock(); - const onSucess = jest.fn(); + const onSuccess = jest.fn(); testRenderer.startServices.http.post.mockResolvedValue({}); - const { result } = testRenderer.renderHook(() => useFleetServerHostsForm([], onSucess)); + const { result } = testRenderer.renderHook(() => + useFleetServerHostsForm( + { + id: 'id1', + name: 'fleet server 1', + host_urls: [], + is_default: false, + is_preconfigured: false, + }, + onSuccess + ) + ); - act(() => result.current.fleetServerHostsInput.props.onChange(['https://test.fr'])); + act(() => result.current.inputs.hostUrlsInput.props.onChange(['https://test.fr'])); await act(() => result.current.submit()); - expect(onSucess).toBeCalled(); + expect(onSuccess).toBeCalled(); }); it('should allow the user to correct and submit a invalid form', async () => { const testRenderer = createFleetTestRendererMock(); - const onSucess = jest.fn(); + const onSuccess = jest.fn(); testRenderer.startServices.http.post.mockResolvedValue({}); - const { result } = testRenderer.renderHook(() => useFleetServerHostsForm([], onSucess)); + const { result } = testRenderer.renderHook(() => + useFleetServerHostsForm( + { + id: 'id1', + name: 'fleet server 1', + host_urls: [], + is_default: false, + is_preconfigured: false, + }, + onSuccess + ) + ); act(() => - result.current.fleetServerHostsInput.props.onChange(['https://test.fr', 'https://test.fr']) + result.current.inputs.hostUrlsInput.props.onChange(['https://test.fr', 'https://test.fr']) ); await act(() => result.current.submit()); - expect(onSucess).not.toBeCalled(); + expect(onSuccess).not.toBeCalled(); expect(result.current.isDisabled).toBeTruthy(); - act(() => result.current.fleetServerHostsInput.props.onChange(['https://test.fr'])); + act(() => result.current.inputs.hostUrlsInput.props.onChange(['https://test.fr'])); expect(result.current.isDisabled).toBeFalsy(); await act(() => result.current.submit()); - expect(onSucess).toBeCalled(); + expect(onSuccess).toBeCalled(); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx index bfe6ffd044140..27d839dc7af59 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx @@ -4,16 +4,23 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +// copy this one import React, { useCallback, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { sendPutSettings, useComboInput, useStartServices } from '../../../../hooks'; +import { + sendPostFleetServerHost, + sendPutFleetServerHost, + useComboInput, + useInput, + useStartServices, + useSwitchInput, +} from '../../../../hooks'; import { isDiffPathProtocol } from '../../../../../../../common/services'; import { useConfirmModal } from '../../hooks/use_confirm_modal'; -import { getAgentAndPolicyCount } from '../../services/agent_and_policies_count'; +import type { FleetServerHost } from '../../../../types'; const URL_REGEX = /^(https):\/\/[^\s$.?#].[^\s]*$/gm; @@ -24,46 +31,14 @@ const ConfirmTitle = () => ( /> ); -interface ConfirmDescriptionProps { - agentCount: number; - agentPolicyCount: number; -} - -const ConfirmDescription: React.FunctionComponent = ({ - agentCount, - agentPolicyCount, -}) => ( +const ConfirmDescription: React.FunctionComponent = ({}) => ( - - - ), - policies: ( - - - - ), - }} + defaultMessage="This action will update agent policies enrolled in this Fleet Server. This action can not be undone. Are you sure you wish to continue?" /> ); -function validateFleetServerHosts(value: string[]) { +export function validateFleetServerHosts(value: string[]) { if (value.length === 0) { return [ { @@ -87,7 +62,7 @@ function validateFleetServerHosts(value: string[]) { } else if (!val.match(URL_REGEX)) { res.push({ message: i18n.translate('xpack.fleet.settings.fleetServerHostsError', { - defaultMessage: 'Invalid URL', + defaultMessage: 'Invalid URL (must be an https URL)', }), index: idx, }); @@ -127,24 +102,41 @@ function validateFleetServerHosts(value: string[]) { } } +export function validateName(value: string) { + if (!value || value === '') { + return [ + i18n.translate('xpack.fleet.settings.fleetServerHost.nameIsRequiredErrorMessage', { + defaultMessage: 'Name is required', + }), + ]; + } +} + export function useFleetServerHostsForm( - fleetServerHostsDefaultValue: string[], + fleetServerHost: FleetServerHost | undefined, onSuccess: () => void ) { const [isLoading, setIsLoading] = useState(false); const { notifications } = useStartServices(); const { confirm } = useConfirmModal(); + const isPreconfigured = fleetServerHost?.is_preconfigured ?? false; - const fleetServerHostsInput = useComboInput( - 'fleetServerHostsInput', - fleetServerHostsDefaultValue, - validateFleetServerHosts + const nameInput = useInput(fleetServerHost?.name ?? '', validateName, isPreconfigured); + const isDefaultInput = useSwitchInput( + fleetServerHost?.is_default ?? false, + isPreconfigured || fleetServerHost?.is_default + ); + + const hostUrlsInput = useComboInput( + 'hostUrls', + fleetServerHost?.host_urls || [], + validateFleetServerHosts, + isPreconfigured ); - const fleetServerHostsInputValidate = fleetServerHostsInput.validate; const validate = useCallback( - () => fleetServerHostsInputValidate(), - [fleetServerHostsInputValidate] + () => hostUrlsInput.validate() && nameInput.validate(), + [hostUrlsInput, nameInput] ); const submit = useCallback(async () => { @@ -152,46 +144,69 @@ export function useFleetServerHostsForm( if (!validate()) { return; } - const { agentCount, agentPolicyCount } = await getAgentAndPolicyCount(); - if ( - !(await confirm( - , - - )) - ) { + if (!(await confirm(, ))) { return; } setIsLoading(true); - const settingsResponse = await sendPutSettings({ - fleet_server_hosts: fleetServerHostsInput.value, - }); - if (settingsResponse.error) { - throw settingsResponse.error; + if (fleetServerHost) { + const res = await sendPutFleetServerHost(fleetServerHost.id, { + name: nameInput.value, + host_urls: hostUrlsInput.value, + is_default: isDefaultInput.value, + }); + if (res.error) { + throw res.error; + } + } else { + const res = await sendPostFleetServerHost({ + name: nameInput.value, + host_urls: hostUrlsInput.value, + is_default: isDefaultInput.value, + }); + if (res.error) { + throw res.error; + } } notifications.toasts.addSuccess( i18n.translate('xpack.fleet.settings.fleetServerHostsFlyout.successToastTitle', { - defaultMessage: 'Settings saved', + defaultMessage: 'Fleet Server host saved', }) ); setIsLoading(false); - onSuccess(); + await onSuccess(); } catch (error) { setIsLoading(false); notifications.toasts.addError(error, { title: i18n.translate('xpack.fleet.settings.fleetServerHostsFlyout.errorToastTitle', { - defaultMessage: 'An error happened while saving settings', + defaultMessage: 'An error happened while saving Fleet Server host', }), }); } - }, [fleetServerHostsInput.value, validate, notifications, confirm, onSuccess]); + }, [ + fleetServerHost, + nameInput.value, + hostUrlsInput.value, + isDefaultInput.value, + validate, + notifications, + confirm, + onSuccess, + ]); const isDisabled = - isLoading || !fleetServerHostsInput.hasChanged || fleetServerHostsInput.props.isInvalid; + isLoading || + (!hostUrlsInput.hasChanged && !isDefaultInput.hasChanged && !nameInput.hasChanged) || + hostUrlsInput.props.isInvalid || + nameInput.props.isInvalid; return { isLoading, isDisabled, submit, - fleetServerHostsInput, + inputs: { + hostUrlsInput, + nameInput, + isDefaultInput, + }, }; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_table/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_table/index.tsx new file mode 100644 index 0000000000000..053baaf4e4f8a --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_table/index.tsx @@ -0,0 +1,156 @@ +/* + * 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, { useMemo } from 'react'; +import styled from 'styled-components'; + +import { + EuiBasicTable, + EuiFlexGroup, + EuiFlexItem, + EuiIconTip, + EuiIcon, + EuiButtonIcon, +} from '@elastic/eui'; +import type { EuiBasicTableColumn } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import type { FleetServerHost } from '../../../../types'; +import { useLink } from '../../../../hooks'; + +export interface FleetServerHostsTableProps { + fleetServerHosts: FleetServerHost[]; + deleteFleetServerHost: (fleetServerHost: FleetServerHost) => void; +} + +const NameFlexItemWithMaxWidth = styled(EuiFlexItem)` + max-width: 250px; +`; + +// Allow child to be truncated +const FlexGroupWithMinWidth = styled(EuiFlexGroup)` + min-width: 0px; +`; + +export const FleetServerHostsTable: React.FunctionComponent = ({ + fleetServerHosts, + deleteFleetServerHost, +}) => { + const { getHref } = useLink(); + + const columns = useMemo((): Array> => { + return [ + { + render: (fleetServerHost: FleetServerHost) => ( + + +

+ {fleetServerHost.name} +

+
+ {fleetServerHost.is_preconfigured && ( + + + + )} +
+ ), + width: '288px', + name: i18n.translate('xpack.fleet.settings.fleetServerHostsTable.nameColumnTitle', { + defaultMessage: 'Name', + }), + }, + { + truncateText: true, + field: 'host_urls', + render: (urls: string[]) => ( + + {urls.map((url) => ( + +

+ {url} +

+
+ ))} +
+ ), + name: i18n.translate('xpack.fleet.settings.fleetServerHostsTable.hostUrlsColumnTitle', { + defaultMessage: 'Host URLs', + }), + }, + { + render: (fleetServerHost: FleetServerHost) => + fleetServerHost.is_default ? ( + + ) : null, + width: '200px', + name: i18n.translate('xpack.fleet.settings.fleetServerHostsTable.defaultColumnTitle', { + defaultMessage: 'Default', + }), + }, + { + width: '68px', + render: (fleetServerHost: FleetServerHost) => { + const isDeleteVisible = !fleetServerHost.is_default && !fleetServerHost.is_preconfigured; + + return ( + + + {isDeleteVisible && ( + deleteFleetServerHost(fleetServerHost)} + title={i18n.translate( + 'xpack.fleet.settings.fleetServerHostsTable.deleteButtonTitle', + { + defaultMessage: 'Delete', + } + )} + data-test-subj="fleetServerHostsTable.delete.btn" + /> + )} + + + + + + ); + }, + name: i18n.translate('xpack.fleet.settings.fleetServerHostsTable.actionsColumnTitle', { + defaultMessage: 'Actions', + }), + }, + ]; + }, [getHref, deleteFleetServerHost]); + + return ; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/multi_row_input/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/multi_row_input/index.tsx index 8bb0a565be040..35165beefca45 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/multi_row_input/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/multi_row_input/index.tsx @@ -373,6 +373,7 @@ export const MultiRowInput: FunctionComponent = ({ {displayErrors(globalErrors)} void; +} + +export const FleetServerHostsSection: React.FunctionComponent = ({ + fleetServerHosts, + deleteFleetServerHost, +}) => { + const { docLinks } = useStartServices(); + const { getHref } = useLink(); + + return ( + <> + +

+ +

+
+ + + + + + ), + }} + /> + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/index.tsx index 4c5db21725639..613221600b99b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/index.tsx @@ -8,31 +8,36 @@ import React from 'react'; import { EuiSpacer } from '@elastic/eui'; -import type { Output, Settings, DownloadSource } from '../../../../types'; +import type { Output, DownloadSource, FleetServerHost } from '../../../../types'; -import { SettingsSection } from './settings_section'; +import { FleetServerHostsSection } from './fleet_server_hosts_section'; import { OutputSection } from './output_section'; import { AgentBinarySection } from './agent_binary_section'; export interface SettingsPageProps { - settings: Settings; outputs: Output[]; + fleetServerHosts: FleetServerHost[]; deleteOutput: (output: Output) => void; + deleteFleetServerHost: (fleetServerHost: FleetServerHost) => void; downloadSources: DownloadSource[]; deleteDownloadSource: (ds: DownloadSource) => void; } export const SettingsPage: React.FunctionComponent = ({ - settings, outputs, + fleetServerHosts, deleteOutput, + deleteFleetServerHost, downloadSources, deleteDownloadSource, }) => { return ( <> - + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/settings_section.stories.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/settings_section.stories.tsx deleted file mode 100644 index 9eab2479b901a..0000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/settings_section.stories.tsx +++ /dev/null @@ -1,44 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { SettingsSection as Component } from './settings_section'; - -export default { - component: Component, - title: 'Sections/Fleet/Settings', -}; - -interface Args { - width: number; - fleetServerHosts: string[]; -} - -const args: Args = { - width: 1200, - fleetServerHosts: [ - 'https://myfleetserver:8220', - 'https://alongerfleetserverwithaverylongname:8220', - ], -}; - -export const SettingsSection = ({ width, fleetServerHosts }: Args) => { - return ( -
- -
- ); -}; - -SettingsSection.args = args; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/settings_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/settings_section.tsx deleted file mode 100644 index bf471a0acd30a..0000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/settings_section.tsx +++ /dev/null @@ -1,110 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useMemo } from 'react'; -import { - EuiTitle, - EuiLink, - EuiText, - EuiSpacer, - EuiBasicTable, - EuiButtonEmpty, - EuiToolTip, -} from '@elastic/eui'; -import type { EuiBasicTableColumn } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { i18n } from '@kbn/i18n'; - -import type { Settings } from '../../../../types'; -import { useLink, useStartServices } from '../../../../hooks'; - -export interface SettingsSectionProps { - settings: Settings; -} - -export const SettingsSection: React.FunctionComponent = ({ settings }) => { - const { docLinks } = useStartServices(); - const { getHref } = useLink(); - - const columns = useMemo((): Array> => { - return [ - { - render: (host: string) => host, - name: i18n.translate('xpack.fleet.settings.fleetServerHostUrlColumnTitle', { - defaultMessage: 'Host URL', - }), - }, - ]; - }, []); - - const isEditDisabled = settings.preconfigured_fields?.includes('fleet_server_hosts') ?? false; - const BtnWrapper = useMemo((): React.FunctionComponent => { - if (!isEditDisabled) { - return ({ children }) => <>{children}; - } - - return ({ children }) => ( - - } - > - <>{children} - - ); - }, [isEditDisabled]); - - return ( - <> - -

- -

-
- - - - - - ), - }} - /> - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/hooks/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/hooks/index.tsx new file mode 100644 index 0000000000000..1fec1c76430eb --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/hooks/index.tsx @@ -0,0 +1,6 @@ +/* + * 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. + */ diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/hooks/use_delete_fleet_server_host.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/hooks/use_delete_fleet_server_host.tsx new file mode 100644 index 0000000000000..7c2046a01e517 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/hooks/use_delete_fleet_server_host.tsx @@ -0,0 +1,70 @@ +/* + * 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 } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; + +import { sendDeleteFleetServerHost, useStartServices } from '../../../hooks'; +import type { FleetServerHost } from '../../../types'; + +import { useConfirmModal } from './use_confirm_modal'; + +const ConfirmTitle = () => ( + +); + +const ConfirmDescription: React.FunctionComponent = ({}) => ( + +); + +export function useDeleteFleetServerHost(onSuccess: () => void) { + const { confirm } = useConfirmModal(); + const { notifications } = useStartServices(); + const deleteFleetServerHost = useCallback( + async (fleetServerHost: FleetServerHost) => { + try { + const isConfirmed = await confirm(, , { + buttonColor: 'danger', + confirmButtonText: i18n.translate( + 'xpack.fleet.settings.deleteFleetServerHosts.confirmButtonLabel', + { + defaultMessage: 'Delete and deploy changes', + } + ), + }); + + if (!isConfirmed) { + return; + } + + const res = await sendDeleteFleetServerHost(fleetServerHost.id); + + if (res.error) { + throw res.error; + } + + onSuccess(); + } catch (err) { + notifications.toasts.addError(err, { + title: i18n.translate('xpack.fleet.settings.deleteFleetServerHosts.errorToastTitle', { + defaultMessage: 'Error deleting Fleet Server hosts', + }), + }); + } + }, + [confirm, notifications.toasts, onSuccess] + ); + + return { deleteFleetServerHost }; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/index.tsx index 7a94d9ef4bc79..bd0b7124a9c77 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/index.tsx @@ -9,7 +9,12 @@ import React, { useCallback } from 'react'; import { EuiPortal } from '@elastic/eui'; import { Router, Route, Switch, useHistory, Redirect } from 'react-router-dom'; -import { useBreadcrumbs, useGetOutputs, useGetSettings, useGetDownloadSources } from '../../hooks'; +import { + useBreadcrumbs, + useGetOutputs, + useGetDownloadSources, + useGetFleetServerHosts, +} from '../../hooks'; import { FLEET_ROUTING_PATHS, pagePathGetters } from '../../constants'; import { DefaultLayout } from '../../layouts'; import { Loading } from '../../components'; @@ -19,6 +24,7 @@ import { withConfirmModalProvider } from './hooks/use_confirm_modal'; import { FleetServerHostsFlyout } from './components/fleet_server_hosts_flyout'; import { EditOutputFlyout } from './components/edit_output_flyout'; import { useDeleteOutput } from './hooks/use_delete_output'; +import { useDeleteFleetServerHost } from './hooks/use_delete_fleet_server_host'; import { EditDownloadSourceFlyout } from './components/download_source_flyout'; import { useDeleteDownloadSource } from './components/download_source_flyout/use_delete_download_source'; @@ -26,29 +32,30 @@ export const SettingsApp = withConfirmModalProvider(() => { useBreadcrumbs('settings'); const history = useHistory(); - const settings = useGetSettings(); const outputs = useGetOutputs(); + const fleetServerHosts = useGetFleetServerHosts(); const downloadSources = useGetDownloadSources(); const { deleteOutput } = useDeleteOutput(outputs.resendRequest); const { deleteDownloadSource } = useDeleteDownloadSource(downloadSources.resendRequest); + const { deleteFleetServerHost } = useDeleteFleetServerHost(fleetServerHosts.resendRequest); - const resendSettingsRequest = settings.resendRequest; const resendOutputRequest = outputs.resendRequest; const resendDownloadSourceRequest = downloadSources.resendRequest; + const resendFleetServerHostsRequest = fleetServerHosts.resendRequest; const onCloseCallback = useCallback(() => { - resendSettingsRequest(); resendOutputRequest(); resendDownloadSourceRequest(); + resendFleetServerHostsRequest(); history.replace(pagePathGetters.settings()[1]); - }, [resendSettingsRequest, resendOutputRequest, resendDownloadSourceRequest, history]); + }, [resendOutputRequest, resendDownloadSourceRequest, resendFleetServerHostsRequest, history]); if ( - (settings.isLoading && settings.isInitialRequest) || - !settings.data?.item || (outputs.isLoading && outputs.isInitialRequest) || !outputs.data?.items || + (fleetServerHosts.isLoading && fleetServerHosts.isInitialRequest) || + !fleetServerHosts.data?.items || (downloadSources.isLoading && downloadSources.isInitialRequest) || !downloadSources.data?.items ) { @@ -64,11 +71,27 @@ export const SettingsApp = withConfirmModalProvider(() => { + {(route: { match: { params: { itemId: string } } }) => { + const fleetServerHost = fleetServerHosts.data?.items.find( + (o) => route.match.params.itemId === o.id + ); + if (!fleetServerHost) { + return ; + } + + return ( + + + + ); + }} + + - + @@ -117,9 +140,10 @@ export const SettingsApp = withConfirmModalProvider(() => { diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts index 371edf0c6f6e9..5865572678ba3 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts @@ -17,6 +17,16 @@ jest.mock('../../hooks/use_request', () => { const module = jest.requireActual('../../hooks/use_request'); return { ...module, + useGetFleetServerHosts: jest.fn().mockReturnValue({ + data: { + items: [ + { + is_default: true, + host_urls: ['http://test.fr'], + }, + ], + }, + }), useGetSettings: jest.fn().mockReturnValue({ data: { item: { fleet_server_hosts: ['test'] } }, }), diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx index 6e46ec90d5faf..ea184084b040e 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx @@ -16,7 +16,11 @@ import { coreMock } from '@kbn/core/public/mocks'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { AgentPolicy } from '../../../common'; -import { useGetSettings, sendGetOneAgentPolicy, useGetAgents } from '../../hooks/use_request'; +import { + useGetFleetServerHosts, + sendGetOneAgentPolicy, + useGetAgents, +} from '../../hooks/use_request'; import { FleetStatusProvider, ConfigContext, @@ -78,8 +82,15 @@ describe('', () => { let testBed: TestBed; beforeEach(() => { - (useGetSettings as jest.Mock).mockReturnValue({ - data: { item: { fleet_server_hosts: ['test'] } }, + (useGetFleetServerHosts as jest.Mock).mockReturnValue({ + data: { + items: [ + { + is_default: true, + host_urls: ['http://test.fr'], + }, + ], + }, }); (useFleetStatus as jest.Mock).mockReturnValue({ isReady: true }); @@ -155,7 +166,6 @@ describe('', () => { describe('managed instructions', () => { it('uses the agent policy selection step', () => { const { exists } = testBed; - expect(exists('agentEnrollmentFlyout')).toBe(true); expect(exists('agent-policy-selection-step')).toBe(true); expect(exists('agent-enrollment-key-selection-step')).toBe(false); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx index cde975fed45dd..9cbd8f66fc246 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx @@ -22,7 +22,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useGetSettings, useFleetStatus, useAgentEnrollmentFlyoutData } from '../../hooks'; +import { useFleetStatus, useAgentEnrollmentFlyoutData, useGetFleetServerHosts } from '../../hooks'; import { FLEET_SERVER_PACKAGE } from '../../constants'; import type { PackagePolicy, AgentPolicy } from '../../types'; @@ -51,9 +51,11 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ return policies.find((p) => p.id === id); }; - const settings = useGetSettings(); + const fleetServerHostsRequest = useGetFleetServerHosts(); + const fleetStatus = useFleetStatus(); - const fleetServerHosts = settings.data?.item?.fleet_server_hosts || []; + const fleetServerHosts = + fleetServerHostsRequest.data?.items?.filter((f) => true)?.[0]?.host_urls ?? []; const [selectedPolicyId, setSelectedPolicyId] = useState(agentPolicy?.id); const [isFleetServerPolicySelected, setIsFleetServerPolicySelected] = useState(false); @@ -92,7 +94,8 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ const { isK8s } = useIsK8sPolicy(selectedPolicy ? selectedPolicy : undefined); - const isLoadingInitialRequest = settings.isLoading && settings.isInitialRequest; + const isLoadingInitialRequest = + fleetServerHostsRequest.isLoading && fleetServerHostsRequest.isInitialRequest; return ( @@ -151,7 +154,7 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ ) : ( { const { agentPolicies, isFleetServerPolicySelected, - settings, + fleetServerHosts, isLoadingAgentPolicies, selectionType, setSelectionType, @@ -66,19 +66,19 @@ export const Instructions = (props: InstructionProps) => { const fleetServers = agents?.items || []; - const fleetServerHosts = useMemo(() => { - return settings?.fleet_server_hosts || []; - }, [settings]); + const displayFleetServerHosts = useMemo(() => { + return fleetServerHosts?.filter((f) => f.is_default)?.[0]?.host_urls || []; + }, [fleetServerHosts]); if (isLoadingAgents || isLoadingAgentPolicies) return ; - const hasNoFleetServerHost = fleetStatus.isReady && fleetServerHosts.length === 0; + const hasNoFleetServerHost = fleetStatus.isReady && displayFleetServerHosts.length === 0; const showAgentEnrollment = fleetStatus.isReady && !isFleetServerUnhealthy && fleetServers.length > 0 && - fleetServerHosts.length > 0; + displayFleetServerHosts.length > 0; const showFleetServerEnrollment = fleetServers.length === 0 || diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx index 1bb7f446ec77c..4bb167fb2494e 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx @@ -186,7 +186,7 @@ export const ManagedSteps: React.FunctionComponent = ({ setSelectedPolicyId, selectedApiKeyId, setSelectedAPIKeyId, - settings, + fleetServerHosts, refreshAgentPolicies, mode, setMode, @@ -207,10 +207,15 @@ export const ManagedSteps: React.FunctionComponent = ({ const enrolledAgentIds = usePollingAgentCount(selectedPolicy?.id || ''); - const fleetServerHosts = useMemo(() => { - return settings?.fleet_server_hosts || []; - }, [settings]); - const installManagedCommands = ManualInstructions(enrollToken, fleetServerHosts, kibanaVersion); + const displayFleetServerHosts = useMemo(() => { + return fleetServerHosts?.filter((f) => f.is_default)?.[0]?.host_urls ?? []; + }, [fleetServerHosts]); + + const installManagedCommands = ManualInstructions( + enrollToken, + displayFleetServerHosts, + kibanaVersion + ); const instructionsSteps = useMemo(() => { const steps: EuiContainedStepProps[] = !agentPolicy diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts index 7c501df0b3f3b..7215cba9e417f 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { AgentPolicy, Settings } from '../../types'; +import type { AgentPolicy, FleetServerHost } from '../../types'; import type { InstalledIntegrationPolicy } from './use_get_agent_incoming_data'; @@ -19,7 +19,7 @@ export interface BaseProps { */ agentPolicy?: AgentPolicy; - settings?: Settings; + fleetServerHosts?: FleetServerHost[]; isFleetServerPolicySelected?: boolean; diff --git a/x-pack/plugins/fleet/public/constants/page_paths.ts b/x-pack/plugins/fleet/public/constants/page_paths.ts index fa9d6c1cec21c..d276d777661ed 100644 --- a/x-pack/plugins/fleet/public/constants/page_paths.ts +++ b/x-pack/plugins/fleet/public/constants/page_paths.ts @@ -16,9 +16,9 @@ export type StaticPage = | 'enrollment_tokens' | 'data_streams' | 'settings' - | 'settings_edit_fleet_server_hosts' | 'settings_create_outputs' | 'settings_create_download_sources' + | 'settings_create_fleet_server_hosts' | 'debug'; export type DynamicPage = @@ -42,7 +42,8 @@ export type DynamicPage = | 'agent_details' | 'agent_details_logs' | 'settings_edit_outputs' - | 'settings_edit_download_sources'; + | 'settings_edit_download_sources' + | 'settings_edit_fleet_server_hosts'; export type Page = StaticPage | DynamicPage; @@ -69,7 +70,8 @@ export const FLEET_ROUTING_PATHS = { enrollment_tokens: '/enrollment-tokens', data_streams: '/data-streams', settings: '/settings', - settings_edit_fleet_server_hosts: '/settings/edit-fleet-server-hosts', + settings_create_fleet_server_hosts: '/settings/create-fleet-server-hosts', + settings_edit_fleet_server_hosts: '/settings/fleet-server-hosts/:itemId', settings_create_outputs: '/settings/create-outputs', settings_edit_outputs: '/settings/outputs/:outputId', settings_create_download_sources: '/settings/create-download-sources', @@ -200,9 +202,13 @@ export const pagePathGetters: { enrollment_tokens: () => [FLEET_BASE_PATH, '/enrollment-tokens'], data_streams: () => [FLEET_BASE_PATH, '/data-streams'], settings: () => [FLEET_BASE_PATH, FLEET_ROUTING_PATHS.settings], - settings_edit_fleet_server_hosts: () => [ + settings_edit_fleet_server_hosts: ({ itemId }) => [ FLEET_BASE_PATH, - FLEET_ROUTING_PATHS.settings_edit_fleet_server_hosts, + FLEET_ROUTING_PATHS.settings_edit_fleet_server_hosts.replace(':itemId', itemId.toString()), + ], + settings_create_fleet_server_hosts: () => [ + FLEET_BASE_PATH, + FLEET_ROUTING_PATHS.settings_create_fleet_server_hosts, ], settings_edit_outputs: ({ outputId }) => [ FLEET_BASE_PATH, diff --git a/x-pack/plugins/fleet/public/hooks/use_request/fleet_server_hosts.ts b/x-pack/plugins/fleet/public/hooks/use_request/fleet_server_hosts.ts new file mode 100644 index 0000000000000..662e0494e20e5 --- /dev/null +++ b/x-pack/plugins/fleet/public/hooks/use_request/fleet_server_hosts.ts @@ -0,0 +1,45 @@ +/* + * 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 { fleetServerHostsRoutesService } from '../../../common/services'; +import type { + GetFleetServerHostsResponse, + PostFleetServerHostsRequest, + PutFleetServerHostsRequest, +} from '../../../common/types/rest_spec/fleet_server_hosts'; + +import { sendRequest, useRequest } from './use_request'; + +export function useGetFleetServerHosts() { + return useRequest({ + method: 'get', + path: fleetServerHostsRoutesService.getListPath(), + }); +} + +export function sendDeleteFleetServerHost(itemId: string) { + return sendRequest({ + method: 'delete', + path: fleetServerHostsRoutesService.getDeletePath(itemId), + }); +} + +export function sendPutFleetServerHost(itemId: string, body: PutFleetServerHostsRequest['body']) { + return sendRequest({ + method: 'put', + path: fleetServerHostsRoutesService.getUpdatePath(itemId), + body, + }); +} + +export function sendPostFleetServerHost(body: PostFleetServerHostsRequest['body']) { + return sendRequest({ + method: 'post', + path: fleetServerHostsRoutesService.getCreatePath(), + body, + }); +} diff --git a/x-pack/plugins/fleet/public/hooks/use_request/index.ts b/x-pack/plugins/fleet/public/hooks/use_request/index.ts index 1ca5297cb22a2..8b1f80b5852fa 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/index.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/index.ts @@ -18,3 +18,4 @@ export * from './setup'; export * from './app'; export * from './ingest_pipelines'; export * from './download_source'; +export * from './fleet_server_hosts'; diff --git a/x-pack/plugins/fleet/public/types/index.ts b/x-pack/plugins/fleet/public/types/index.ts index 2438272503ac7..7554d2ba0f45e 100644 --- a/x-pack/plugins/fleet/public/types/index.ts +++ b/x-pack/plugins/fleet/public/types/index.ts @@ -24,6 +24,7 @@ export type { PackagePolicyPackage, Output, DownloadSource, + FleetServerHost, DataStream, Settings, ActionStatus, diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts index 1059d4a44933f..4ce84ea96eca3 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts @@ -37,12 +37,13 @@ function mockAgentPolicy(data: Partial) { }); } -jest.mock('../settings', () => { +jest.mock('../fleet_server_host', () => { return { - getSettings: () => { + getFleetServerHostsForAgentPolicy: async () => { return { id: '93f74c0-e876-11ea-b7d3-8b2acec6f75c', - fleet_server_hosts: ['http://fleetserver:8220'], + is_default: true, + host_urls: ['http://fleetserver:8220'], }; }, }; diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts index 283d3d57faae6..3a1ba1d33abc9 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts @@ -8,24 +8,19 @@ import type { SavedObjectsClientContract } from '@kbn/core/server'; import { safeLoad } from 'js-yaml'; -import type { - FullAgentPolicy, - PackagePolicy, - Settings, - Output, - FullAgentPolicyOutput, -} from '../../types'; +import type { FullAgentPolicy, PackagePolicy, Output, FullAgentPolicyOutput } from '../../types'; import { agentPolicyService } from '../agent_policy'; import { outputService } from '../output'; import { dataTypes, outputType } from '../../../common/constants'; import type { FullAgentPolicyOutputPermissions, PackageInfo } from '../../../common/types'; -import { getSettings } from '../settings'; import { DEFAULT_OUTPUT } from '../../constants'; import { getSourceUriForAgentPolicy } from '../../routes/agent/source_uri_utils'; import { getPackageInfo } from '../epm/packages'; import { pkgToPkgKey, splitPkgKey } from '../epm/registry'; +import { getFleetServerHostsForAgentPolicy } from '../fleet_server_host'; +import { appContextService } from '../app_context'; import { getMonitoringPermissions } from './monitoring_permissions'; import { storedPackagePoliciesToAgentInputs } from '.'; @@ -188,17 +183,19 @@ export async function getFullAgentPolicy( return outputPermissions; }, {}); - // only add settings if not in standalone + // only add fleet server hosts if not in standalone if (!standalone) { - let settings: Settings; - try { - settings = await getSettings(soClient); - } catch (error) { - throw new Error('Default settings is not setup'); - } - if (settings.fleet_server_hosts && settings.fleet_server_hosts.length) { + const fleetServerHost = await getFleetServerHostsForAgentPolicy(soClient, agentPolicy).catch( + (err) => { + appContextService.getLogger()?.error(err); + + return; + } + ); + + if (fleetServerHost) { fullAgentPolicy.fleet = { - hosts: settings.fleet_server_hosts, + hosts: fleetServerHost.host_urls, }; } } diff --git a/x-pack/plugins/fleet/server/services/agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policy.test.ts index 8dd8c885af3e4..5ed4b0a290c93 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.test.ts @@ -391,6 +391,29 @@ describe('agent policy', () => { mockedOutputService.getDefaultDataOutputId.mockResolvedValue('default-output'); mockedGetFullAgentPolicy.mockResolvedValue(null); + const mockFleetServerHost = { + id: 'id1', + name: 'fleet server 1', + host_urls: ['https://host1.fr:8220', 'https://host2-with-a-longer-name.fr:8220'], + is_default: false, + is_preconfigured: false, + }; + soClient.find.mockResolvedValue({ + saved_objects: [ + { + id: 'existing-fleet-server-host', + type: 'fleet-fleet-server-host', + score: 1, + references: [], + version: '1.0.0', + attributes: mockFleetServerHost, + }, + ], + page: 0, + per_page: 0, + total: 0, + }); + const mockSo = { attributes: {}, id: 'policy123', @@ -428,6 +451,28 @@ describe('agent policy', () => { references: [], }; soClient.get.mockResolvedValue(mockSo); + const mockFleetServerHost = { + id: 'id1', + name: 'fleet server 1', + host_urls: ['https://host1.fr:8220', 'https://host2-with-a-longer-name.fr:8220'], + is_default: false, + is_preconfigured: false, + }; + soClient.find.mockResolvedValue({ + saved_objects: [ + { + id: 'existing-fleet-server-host', + type: 'fleet-fleet-server-host', + score: 1, + references: [], + version: '1.0.0', + attributes: mockFleetServerHost, + }, + ], + page: 0, + per_page: 0, + total: 0, + }); soClient.bulkGet.mockResolvedValue({ saved_objects: [mockSo], }); diff --git a/x-pack/plugins/fleet/server/services/fleet_server_host.test.ts b/x-pack/plugins/fleet/server/services/fleet_server_host.test.ts index 72602df6af3db..40f65ca21c5ef 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server_host.test.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server_host.test.ts @@ -13,54 +13,7 @@ import { DEFAULT_FLEET_SERVER_HOST_ID, } from '../constants'; -import { appContextService } from './app_context'; import { migrateSettingsToFleetServerHost } from './fleet_server_host'; -import { getCloudFleetServersHosts } from './settings'; - -jest.mock('./app_context'); - -const mockedAppContextService = appContextService as jest.Mocked; - -describe('getCloudFleetServersHosts', () => { - afterEach(() => { - mockedAppContextService.getCloud.mockReset(); - }); - it('should return undefined if cloud is not setup', () => { - expect(getCloudFleetServersHosts()).toBeUndefined(); - }); - - it('should return fleet server hosts if cloud is correctly setup with default port == 443', () => { - mockedAppContextService.getCloud.mockReturnValue({ - cloudId: - 'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==', - isCloudEnabled: true, - deploymentId: 'deployment-id-1', - apm: {}, - }); - - expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(` - Array [ - "https://deployment-id-1.fleet.us-east-1.aws.found.io", - ] - `); - }); - - it('should return fleet server hosts if cloud is correctly setup with a default port', () => { - mockedAppContextService.getCloud.mockReturnValue({ - cloudId: - 'test:dGVzdC5mcjo5MjQzJGRhM2I2YjNkYWY5ZDRjODE4ZjI4ZmEzNDdjMzgzODViJDgxMmY4NWMxZjNjZTQ2YTliYjgxZjFjMWIxMzRjNmRl', - isCloudEnabled: true, - deploymentId: 'deployment-id-1', - apm: {}, - }); - - expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(` - Array [ - "https://deployment-id-1.fleet.test.fr:9243", - ] - `); - }); -}); describe('migrateSettingsToFleetServerHost', () => { it('should not migrate settings if a default fleet server policy config exists', async () => { diff --git a/x-pack/plugins/fleet/server/services/fleet_server_host.ts b/x-pack/plugins/fleet/server/services/fleet_server_host.ts index a3ade854770c3..42f03d6e269c0 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server_host.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server_host.ts @@ -7,6 +7,7 @@ import type { SavedObjectsClientContract } from '@kbn/core/server'; +import { normalizeHostsForAgents } from '../../common/services'; import { GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, @@ -19,6 +20,7 @@ import type { FleetServerHostSOAttributes, FleetServerHost, NewFleetServerHost, + AgentPolicy, } from '../types'; import { FleetServerHostUnauthorizedError } from '../errors'; @@ -39,6 +41,10 @@ export async function createFleetServerHost( } } + if (data.host_urls) { + data.host_urls = data.host_urls.map(normalizeHostsForAgents); + } + const res = await soClient.create( FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, data, @@ -133,6 +139,10 @@ export async function updateFleetServerHost( } } + if (data.host_urls) { + data.host_urls = data.host_urls.map(normalizeHostsForAgents); + } + await soClient.update(FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, id, data); return { @@ -177,6 +187,22 @@ export async function bulkGetFleetServerHosts( ); } +export async function getFleetServerHostsForAgentPolicy( + soClient: SavedObjectsClientContract, + agentPolicy: AgentPolicy +) { + if (agentPolicy.fleet_server_host_id) { + return getFleetServerHost(soClient, agentPolicy.fleet_server_host_id); + } + + const defaultFleetServerHost = await getDefaultFleetServerHost(soClient); + if (!defaultFleetServerHost) { + throw new Error('Default Fleet Server host is not setup'); + } + + return defaultFleetServerHost; +} + /** * Get the default Fleet server policy hosts or throw if it does not exists */ diff --git a/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.test.ts index 468058f87f448..685f12f21cb4d 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.test.ts @@ -5,9 +5,16 @@ * 2.0. */ -import { getPreconfiguredFleetServerHostFromConfig } from './fleet_server_host'; +import { appContextService } from '../app_context'; -jest.mock('../fleet_server_host'); +import { + getCloudFleetServersHosts, + getPreconfiguredFleetServerHostFromConfig, +} from './fleet_server_host'; + +jest.mock('../app_context'); + +const mockedAppContextService = appContextService as jest.Mocked; describe('getPreconfiguredFleetServerHostFromConfig', () => { it('should work with preconfigured fleetServerHosts', () => { @@ -81,3 +88,44 @@ describe('getPreconfiguredFleetServerHostFromConfig', () => { ); }); }); + +describe('getCloudFleetServersHosts', () => { + afterEach(() => { + mockedAppContextService.getCloud.mockReset(); + }); + it('should return undefined if cloud is not setup', () => { + expect(getCloudFleetServersHosts()).toBeUndefined(); + }); + + it('should return fleet server hosts if cloud is correctly setup with default port == 443', () => { + mockedAppContextService.getCloud.mockReturnValue({ + cloudId: + 'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==', + isCloudEnabled: true, + deploymentId: 'deployment-id-1', + apm: {}, + }); + + expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(` + Array [ + "https://deployment-id-1.fleet.us-east-1.aws.found.io", + ] + `); + }); + + it('should return fleet server hosts if cloud is correctly setup with a default port', () => { + mockedAppContextService.getCloud.mockReturnValue({ + cloudId: + 'test:dGVzdC5mcjo5MjQzJGRhM2I2YjNkYWY5ZDRjODE4ZjI4ZmEzNDdjMzgzODViJDgxMmY4NWMxZjNjZTQ2YTliYjgxZjFjMWIxMzRjNmRl', + isCloudEnabled: true, + deploymentId: 'deployment-id-1', + apm: {}, + }); + + expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(` + Array [ + "https://deployment-id-1.fleet.test.fr:9243", + ] + `); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.ts b/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.ts index 465a2f8706ea9..13de4e8ec64b5 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.ts @@ -8,10 +8,12 @@ import type { SavedObjectsClientContract } from '@kbn/core/server'; import { isEqual } from 'lodash'; +import { decodeCloudId, normalizeHostsForAgents } from '../../../common/services'; import type { FleetConfigType } from '../../config'; import { DEFAULT_FLEET_SERVER_HOST_ID } from '../../constants'; import type { FleetServerHost } from '../../types'; +import { appContextService } from '../app_context'; import { bulkGetFleetServerHosts, createFleetServerHost, @@ -20,12 +22,42 @@ import { updateFleetServerHost, } from '../fleet_server_host'; +export function getCloudFleetServersHosts() { + const cloudSetup = appContextService.getCloud(); + if (cloudSetup && cloudSetup.isCloudEnabled && cloudSetup.cloudId && cloudSetup.deploymentId) { + const res = decodeCloudId(cloudSetup.cloudId); + if (!res) { + return; + } + + // Fleet Server url are formed like this `https://.fleet. + return [ + `https://${cloudSetup.deploymentId}.fleet.${res.host}${ + res.defaultPort !== '443' ? `:${res.defaultPort}` : '' + }`, + ]; + } +} + export function getPreconfiguredFleetServerHostFromConfig(config?: FleetConfigType) { const { fleetServerHosts: fleetServerHostsFromConfig } = config; const legacyFleetServerHostsConfig = getConfigFleetServerHosts(config); + // is cloud + const cloudServerHosts = getCloudFleetServersHosts(); + const fleetServerHosts: FleetServerHost[] = (fleetServerHostsFromConfig || []).concat([ + ...(cloudServerHosts + ? [ + { + name: 'Default', + is_default: true, + id: DEFAULT_FLEET_SERVER_HOST_ID, + host_urls: cloudServerHosts, + }, + ] + : []), ...(legacyFleetServerHostsConfig ? [ { @@ -77,7 +109,10 @@ export async function createOrUpdatePreconfiguredFleetServerHosts( (!existingHost.is_preconfigured || existingHost.is_default !== preconfiguredFleetServerHost.is_default || existingHost.name !== preconfiguredFleetServerHost.name || - !isEqual(existingHost?.host_urls, preconfiguredFleetServerHost.host_urls)); + !isEqual( + existingHost.host_urls.map(normalizeHostsForAgents), + preconfiguredFleetServerHost.host_urls.map(normalizeHostsForAgents) + )); if (isCreate) { await createFleetServerHost( diff --git a/x-pack/plugins/fleet/server/services/settings.test.ts b/x-pack/plugins/fleet/server/services/settings.test.ts index 642553f0db674..ca0be8130b21a 100644 --- a/x-pack/plugins/fleet/server/services/settings.test.ts +++ b/x-pack/plugins/fleet/server/services/settings.test.ts @@ -8,53 +8,12 @@ import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { appContextService } from './app_context'; -import { getCloudFleetServersHosts, settingsSetup } from './settings'; +import { settingsSetup } from './settings'; jest.mock('./app_context'); const mockedAppContextService = appContextService as jest.Mocked; -describe('getCloudFleetServersHosts', () => { - afterEach(() => { - mockedAppContextService.getCloud.mockReset(); - }); - it('should return undefined if cloud is not setup', () => { - expect(getCloudFleetServersHosts()).toBeUndefined(); - }); - - it('should return fleet server hosts if cloud is correctly setup with default port == 443', () => { - mockedAppContextService.getCloud.mockReturnValue({ - cloudId: - 'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==', - isCloudEnabled: true, - deploymentId: 'deployment-id-1', - apm: {}, - }); - - expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(` - Array [ - "https://deployment-id-1.fleet.us-east-1.aws.found.io", - ] - `); - }); - - it('should return fleet server hosts if cloud is correctly setup with a default port', () => { - mockedAppContextService.getCloud.mockReturnValue({ - cloudId: - 'test:dGVzdC5mcjo5MjQzJGRhM2I2YjNkYWY5ZDRjODE4ZjI4ZmEzNDdjMzgzODViJDgxMmY4NWMxZjNjZTQ2YTliYjgxZjFjMWIxMzRjNmRl', - isCloudEnabled: true, - deploymentId: 'deployment-id-1', - apm: {}, - }); - - expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(` - Array [ - "https://deployment-id-1.fleet.test.fr:9243", - ] - `); - }); -}); - describe('settingsSetup', () => { afterEach(() => { mockedAppContextService.getCloud.mockReset(); @@ -82,7 +41,7 @@ describe('settingsSetup', () => { expect(soClientMock.create).toBeCalled(); }); - it('should do nothing if there is settings and no default fleet server hosts', async () => { + it('should do nothing if there is settings', async () => { const soClientMock = savedObjectsClientMock.create(); soClientMock.find.mockResolvedValue({ @@ -111,204 +70,4 @@ describe('settingsSetup', () => { expect(soClientMock.create).not.toBeCalled(); }); - - it('should update settings if there is settings without fleet server hosts and default fleet server hosts', async () => { - const soClientMock = savedObjectsClientMock.create(); - mockedAppContextService.getCloud.mockReturnValue({ - cloudId: - 'test:dGVzdC5mcjo5MjQzJGRhM2I2YjNkYWY5ZDRjODE4ZjI4ZmEzNDdjMzgzODViJDgxMmY4NWMxZjNjZTQ2YTliYjgxZjFjMWIxMzRjNmRl', - isCloudEnabled: true, - deploymentId: 'deployment-id-1', - apm: {}, - }); - - soClientMock.find.mockResolvedValue({ - total: 1, - page: 0, - per_page: 10, - saved_objects: [ - { - id: 'defaultsettings', - attributes: {}, - type: 'so_type', - references: [], - score: 0, - }, - ], - }); - - soClientMock.update.mockResolvedValue({ - id: 'updated', - attributes: {}, - references: [], - type: 'so_type', - }); - - soClientMock.create.mockResolvedValue({ - id: 'created', - attributes: {}, - references: [], - type: 'so_type', - }); - - await settingsSetup(soClientMock); - - expect(soClientMock.create).not.toBeCalled(); - expect(soClientMock.update).toBeCalledWith('ingest_manager_settings', 'defaultsettings', { - fleet_server_hosts: ['https://deployment-id-1.fleet.test.fr:9243'], - }); - }); - - it('should update settings if there is a new fleet server host in the config', async () => { - const soClientMock = savedObjectsClientMock.create(); - mockedAppContextService.getCloud.mockReturnValue({ - cloudId: - 'test:dGVzdC5mcjo5MjQzJGRhM2I2YjNkYWY5ZDRjODE4ZjI4ZmEzNDdjMzgzODViJDgxMmY4NWMxZjNjZTQ2YTliYjgxZjFjMWIxMzRjNmRl', - isCloudEnabled: true, - deploymentId: 'deployment-id-1', - apm: {}, - }); - mockedAppContextService.getConfig.mockReturnValue({ - agents: { - fleet_server: { hosts: ['http://fleetserverupdated.fr:8220'] }, - }, - } as any); - - soClientMock.find.mockResolvedValue({ - total: 1, - page: 0, - per_page: 10, - saved_objects: [ - { - id: 'defaultsettings', - attributes: { - fleet_server_hosts: ['https://deployment-id-1.fleet.test.fr:9243'], - }, - type: 'so_type', - references: [], - score: 0, - }, - ], - }); - - soClientMock.update.mockResolvedValue({ - id: 'updated', - attributes: {}, - references: [], - type: 'so_type', - }); - - soClientMock.create.mockResolvedValue({ - id: 'created', - attributes: {}, - references: [], - type: 'so_type', - }); - - await settingsSetup(soClientMock); - - expect(soClientMock.create).not.toBeCalled(); - expect(soClientMock.update).toBeCalledWith('ingest_manager_settings', 'defaultsettings', { - fleet_server_hosts: ['http://fleetserverupdated.fr:8220'], - }); - }); - - it('should update settings if there is no new fleet server hosts in the config', async () => { - const soClientMock = savedObjectsClientMock.create(); - mockedAppContextService.getCloud.mockReturnValue({ - cloudId: - 'test:dGVzdC5mcjo5MjQzJGRhM2I2YjNkYWY5ZDRjODE4ZjI4ZmEzNDdjMzgzODViJDgxMmY4NWMxZjNjZTQ2YTliYjgxZjFjMWIxMzRjNmRl', - isCloudEnabled: true, - deploymentId: 'deployment-id-1', - apm: {}, - }); - mockedAppContextService.getConfig.mockReturnValue({ - agents: { - fleet_server: { hosts: ['http://fleetserverupdated.fr:8220'] }, - }, - } as any); - - soClientMock.find.mockResolvedValue({ - total: 1, - page: 0, - per_page: 10, - saved_objects: [ - { - id: 'defaultsettings', - attributes: { - fleet_server_hosts: ['http://fleetserverupdated.fr:8220'], - }, - type: 'so_type', - references: [], - score: 0, - }, - ], - }); - - soClientMock.update.mockResolvedValue({ - id: 'updated', - attributes: {}, - references: [], - type: 'so_type', - }); - - soClientMock.create.mockResolvedValue({ - id: 'created', - attributes: {}, - references: [], - type: 'so_type', - }); - - await settingsSetup(soClientMock); - - expect(soClientMock.create).not.toBeCalled(); - expect(soClientMock.update).not.toBeCalled(); - }); - - it('should not update settings with cloud settings if there is settings with fleet server hosts and default fleet server hosts', async () => { - const soClientMock = savedObjectsClientMock.create(); - mockedAppContextService.getCloud.mockReturnValue({ - cloudId: - 'test:dGVzdC5mcjo5MjQzJGRhM2I2YjNkYWY5ZDRjODE4ZjI4ZmEzNDdjMzgzODViJDgxMmY4NWMxZjNjZTQ2YTliYjgxZjFjMWIxMzRjNmRl', - isCloudEnabled: true, - deploymentId: 'deployment-id-1', - apm: {}, - }); - - soClientMock.find.mockResolvedValue({ - total: 1, - page: 0, - per_page: 10, - saved_objects: [ - { - id: 'defaultsettings', - attributes: { - fleet_server_hosts: ['http://fleetserver:1234'], - }, - type: 'so_type', - references: [], - score: 0, - }, - ], - }); - - soClientMock.update.mockResolvedValue({ - id: 'updated', - attributes: {}, - references: [], - type: 'so_type', - }); - - soClientMock.create.mockResolvedValue({ - id: 'created', - attributes: {}, - references: [], - type: 'so_type', - }); - - await settingsSetup(soClientMock); - - expect(soClientMock.create).not.toBeCalled(); - expect(soClientMock.update).not.toBeCalled(); - }); }); diff --git a/x-pack/plugins/fleet/server/services/settings.ts b/x-pack/plugins/fleet/server/services/settings.ts index 41b30acc512eb..5cde2dbf99815 100644 --- a/x-pack/plugins/fleet/server/services/settings.ts +++ b/x-pack/plugins/fleet/server/services/settings.ts @@ -6,10 +6,9 @@ */ import Boom from '@hapi/boom'; -import { isEqual } from 'lodash'; import type { SavedObjectsClientContract } from '@kbn/core/server'; -import { decodeCloudId, normalizeHostsForAgents } from '../../common/services'; +import { normalizeHostsForAgents } from '../../common/services'; import { GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, GLOBAL_SETTINGS_ID } from '../../common/constants'; import type { SettingsSOAttributes, Settings, BaseSettings } from '../../common/types'; @@ -34,23 +33,7 @@ export async function getSettings(soClient: SavedObjectsClientContract): Promise export async function settingsSetup(soClient: SavedObjectsClientContract) { try { - const settings = await getSettings(soClient); - const defaultSettings = createDefaultSettings(); - - const fleetServerHostsIsPreconfigured = getConfigFleetServerHosts()?.length ?? 0 > 0; - - const fleetServerHostsShouldBeUpdated = - !settings.fleet_server_hosts || - settings.fleet_server_hosts.length === 0 || - (fleetServerHostsIsPreconfigured && - !isEqual(settings.fleet_server_hosts, defaultSettings.fleet_server_hosts)); - - // Migration for < 7.13 Kibana - if (defaultSettings.fleet_server_hosts.length > 0 && fleetServerHostsShouldBeUpdated) { - return saveSettings(soClient, { - fleet_server_hosts: defaultSettings.fleet_server_hosts, - }); - } + await getSettings(soClient); } catch (e) { if (e.isBoom && e.output.statusCode === 404) { const defaultSettings = createDefaultSettings(); @@ -116,29 +99,5 @@ function getConfigFleetServerHosts() { } export function createDefaultSettings(): BaseSettings { - const configFleetServerHosts = getConfigFleetServerHosts(); - const cloudFleetServerHosts = getCloudFleetServersHosts(); - - const fleetServerHosts = configFleetServerHosts ?? cloudFleetServerHosts ?? []; - - return { - fleet_server_hosts: fleetServerHosts, - }; -} - -export function getCloudFleetServersHosts() { - const cloudSetup = appContextService.getCloud(); - if (cloudSetup && cloudSetup.isCloudEnabled && cloudSetup.cloudId && cloudSetup.deploymentId) { - const res = decodeCloudId(cloudSetup.cloudId); - if (!res) { - return; - } - - // Fleet Server url are formed like this `https://.fleet. - return [ - `https://${cloudSetup.deploymentId}.fleet.${res.host}${ - res.defaultPort !== '443' ? `:${res.defaultPort}` : '' - }`, - ]; - } + return {}; } diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index 37f368a4b8647..1f39062bf9393 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -83,12 +83,13 @@ async function createSetupSideEffects( logger.debug('Setting up Fleet download source'); const defaultDownloadSource = await downloadSourceService.ensureDefault(soClient); - logger.debug('Setting up Fleet outputs'); - + logger.debug('Setting up Fleet Sever Hosts'); await ensurePreconfiguredFleetServerHosts( soClient, getPreconfiguredFleetServerHostFromConfig(appContextService.getConfig()) ); + + logger.debug('Setting up Fleet outputs'); await Promise.all([ ensurePreconfiguredOutputs( soClient, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index d0de64daf9ad0..7d1b626c1c7e8 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -12699,7 +12699,6 @@ "xpack.fleet.epmList.verificationWarningCalloutIntroText": "Une ou plusieurs des intégrations installées contiennent un package non signé à l'authenticité inconnue. En savoir plus sur {learnMoreLink}.", "xpack.fleet.fleetServerCloudRequiredCallout.calloutDescription": "Un serveur Fleet intègre est nécessaire pour enregistrer des agents avec Fleet. Activez un serveur Fleet dans votre {cloudDeploymentLink}. Pour en savoir plus, consultez le {guideLink}.", "xpack.fleet.fleetServerFlyout.generateFleetServerPolicySuccessInstructions": "La politique du serveur Fleet et le token de service ont été générés. Hôte configuré sur {hostUrl}. Vous pouvez modifier les hôtes de votre serveur Fleet dans {fleetSettingsLink}.", - "xpack.fleet.fleetServerFlyout.getStartedInstructions": "Tout d'abord, définissez l'IP public ou le nom d'hôte et le port que les agents utiliseront pour atteindre le serveur Fleet. Par défaut, le port {port} est utilisé. Nous générerons ensuite automatiquement une politique à votre place.", "xpack.fleet.fleetServerFlyout.installFleetServerInstructions": "Installez l'agent du serveur Fleet sur un hôte centralisé afin que les autres hôtes que vous souhaitez monitorer puissent s'y connecter. En production, nous recommandons d'utiliser un ou plusieurs hôtes dédiés. Pour une aide supplémentaire, consultez nos {installationLink}.", "xpack.fleet.fleetServerFlyout.instructions": "Un serveur Fleet est nécessaire pour enregistrer des agents avec Fleet. Suivez les instructions ci-après pour configurer un serveur Fleet. Pour en savoir plus, consultez le {userGuideLink}", "xpack.fleet.fleetServerOnPremRequiredCallout.calloutDescription": "Suivez les instructions ci-après pour configurer un serveur Fleet. Pour en savoir plus, consultez le {guideLink}.", @@ -12778,9 +12777,6 @@ "xpack.fleet.settings.editOutputFlyout.defaultOutputSwitchLabel": "Choisissez cette sortie par défaut pour les {boldAgentIntegrations}.", "xpack.fleet.settings.editOutputFlyout.logstashHostsInputDescription": "Spécifiez les adresses que vos agents utiliseront pour se connecter à Logstash. {guideLink}.", "xpack.fleet.settings.fleetServerHostSectionSubtitle": "Précisez les URL que vos agents doivent utiliser pour se connecter à un serveur Fleet. Si plusieurs URL existent, Fleet affichera la première URL fournie à des fins d'enregistrement. Pour en savoir plus, consultez le {guideLink}.", - "xpack.fleet.settings.fleetServerHostsFlyout.agentPolicyCount": "{agentPolicyCount, plural, one {# stratégie d’agent} other {# stratégies d’agent}}", - "xpack.fleet.settings.fleetServerHostsFlyout.agentsCount": "{agentCount, plural, one {# agent} other {# agents}}", - "xpack.fleet.settings.fleetServerHostsFlyout.confirmModalText": "Cette action mettra à jour les {policies} et {agents}. Impossible d'annuler cette action. Voulez-vous vraiment continuer ?", "xpack.fleet.settings.fleetServerHostsFlyout.description": "Précisez les URL que vos agents doivent utiliser pour se connecter à un serveur Fleet. Si plusieurs URL existent, Fleet affiche la première URL fournie à des fins d'enregistrement. Le serveur Fleet utilise le port 8220 par défaut. Reportez-vous à {link}.", "xpack.fleet.settings.logstashInstructions.addPipelineStepDescription": "Dans le répertoire de configuration Logstash, ouvrez le fichier {pipelineFile} et ajoutez la configuration suivante. Remplacez le chemin d’accès à votre fichier.", "xpack.fleet.settings.logstashInstructions.description": "Ajoutez une configuration de pipeline Elastic Agent à Logstash pour recevoir les événements du framework Elastic Agent. {documentationLink}.", @@ -13398,8 +13394,6 @@ "xpack.fleet.fleetServerFlyout.getStartedTitle": "Démarrer avec le serveur Fleet", "xpack.fleet.fleetServerFlyout.installFleetServerTitle": "Installer le serveur Fleet sur un hôte centralisé", "xpack.fleet.fleetServerFlyout.title": "Ajouter un serveur Fleet", - "xpack.fleet.fleetServerHost.requiredError": "L'hôte du serveur Fleet est requis.", - "xpack.fleet.fleetServerHost.requiresHttpsError": "L'hôte du serveur Fleet doit commencer par \"https\"", "xpack.fleet.fleetServerOnPremRequiredCallout.calloutTitle": "Un serveur Fleet est nécessaire pour enregistrer des agents avec Fleet.", "xpack.fleet.fleetServerOnPremRequiredCallout.guideLink": "Guide de Fleet et d’Elastic Agent", "xpack.fleet.fleetServerOnPremUnhealthyCallout.addFleetServerButtonLabel": "Ajouter un serveur Fleet", @@ -13407,7 +13401,6 @@ "xpack.fleet.fleetServerOnPremUnhealthyCallout.guideLink": "Guide de Fleet et d’Elastic Agent", "xpack.fleet.fleetServerSetup.addFleetServerHostButton": "Ajouter un hôte", "xpack.fleet.fleetServerSetup.addFleetServerHostInputLabel": "Hôte du serveur Fleet", - "xpack.fleet.fleetServerSetup.addFleetServerHostInvalidUrlError": "URL non valide", "xpack.fleet.fleetServerSetup.addFleetServerHostStepTitle": "Ajouter l'hôte de votre serveur Fleet", "xpack.fleet.fleetServerSetup.addFleetServerHostSuccessTitle": "Hôte du serveur Fleet ajouté", "xpack.fleet.fleetServerSetup.cloudDeploymentLink": "Modifier le déploiement", @@ -13608,7 +13601,6 @@ "xpack.fleet.settings.editOutputFlyout.typeInputPlaceholder": "Indiquer le type", "xpack.fleet.settings.editOutputFlyout.yamlConfigInputLabel": "Configuration YAML avancée", "xpack.fleet.settings.editOutputFlyout.yamlConfigInputPlaceholder": "Ces # paramètres YAML seront ajoutés à la section de sortie de chaque stratégie d’agent.", - "xpack.fleet.settings.fleetServerHostEditButtonLabel": "Modifier les hôtes", "xpack.fleet.settings.fleetServerHostsDifferentPathOrProtocolError": "Le protocole et le chemin doivent être identiques pour chaque URL", "xpack.fleet.settings.fleetServerHostsDuplicateError": "URL en double", "xpack.fleet.settings.fleetServerHostSectionTitle": "Hôtes du serveur Fleet", @@ -13620,11 +13612,8 @@ "xpack.fleet.settings.fleetServerHostsFlyout.fleetServerHostsInputPlaceholder": "Indiquer l’URL de l’hôte", "xpack.fleet.settings.fleetServerHostsFlyout.saveButton": "Enregistrer et appliquer les paramètres", "xpack.fleet.settings.fleetServerHostsFlyout.successToastTitle": "Paramètres enregistrés", - "xpack.fleet.settings.fleetServerHostsFlyout.title": "Hôtes du serveur Fleet", "xpack.fleet.settings.fleetServerHostsFlyout.userGuideLink": "Guide de Fleet et d'Elastic Agent", - "xpack.fleet.settings.fleetServerHostsPreconfiguredTooltipContent": "Les hôtes du serveur Fleet sont configurés en dehors de Fleet. Reportez-vous à votre configuration Kibana pour en savoir plus.", "xpack.fleet.settings.fleetServerHostsRequiredError": "L'URL de l'hôte est requise", - "xpack.fleet.settings.fleetServerHostUrlColumnTitle": "URL de l’hôte", "xpack.fleet.settings.fleetSettingsLink": "En savoir plus", "xpack.fleet.settings.fleetUserGuideLink": "Guide de Fleet et d'Elastic Agent", "xpack.fleet.settings.logstashInstructions.apiKeyStepDescription": "Nous recommandons d'autoriser Logstash à effectuer la sortie vers Elasticsearch avec les privilèges minimum pour Elastic Agent.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e1b669456bece..d3f6cb84354ae 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12684,7 +12684,6 @@ "xpack.fleet.epmList.verificationWarningCalloutIntroText": "1つ以上のインストールされた統合には、真正が不明な未署名のパッケージが含まれています。{learnMoreLink}の詳細をご覧ください。", "xpack.fleet.fleetServerCloudRequiredCallout.calloutDescription": "Fleetにエージェントを登録するには、Fleetサーバーが必要です。{cloudDeploymentLink}でFleetサーバーを有効にします。詳細については、{guideLink}を参照してください。", "xpack.fleet.fleetServerFlyout.generateFleetServerPolicySuccessInstructions": "Fleetサーバーポリシーとサービストークンが生成されました。ホストが{hostUrl}で構成されました。 {fleetSettingsLink}でFleetサーバーを編集できます。", - "xpack.fleet.fleetServerFlyout.getStartedInstructions": "まず、エージェントがFleetサーバーに接続するために使用する、公開IPまたはホスト名とポートを設定します。デフォルトでは、ポート{port}が使用されます。これで、自動的にポリシーが生成されます。", "xpack.fleet.fleetServerFlyout.installFleetServerInstructions": "Fleetサーバーエージェントを一元化されたホストにインストールし、監視する他のホストがそれに接続できるようにします。本番では、1つ以上の専用ホストを使用することをお勧めします。詳細なガイダンスについては、{installationLink}を参照してください。", "xpack.fleet.fleetServerFlyout.instructions": "Fleetにエージェントを登録する前に、Fleetサーバーが必要です。Fleetサーバーのセットアップについては、次の手順に従ってください。詳細については、{userGuideLink}を参照してください。", "xpack.fleet.fleetServerOnPremRequiredCallout.calloutDescription": "Fleetサーバーのセットアップについては、次の手順に従ってください。詳細については、{guideLink}を参照してください。", @@ -12764,9 +12763,6 @@ "xpack.fleet.settings.editOutputFlyout.defaultOutputSwitchLabel": "この出力を{boldAgentIntegrations}のデフォルトにします。", "xpack.fleet.settings.editOutputFlyout.logstashHostsInputDescription": "エージェントがLogstashに接続するために使用するアドレスを指定します。{guideLink}。", "xpack.fleet.settings.fleetServerHostSectionSubtitle": "エージェントがFleetサーバーに接続するために使用するURLを指定します。複数のURLが存在する場合、Fleetは登録目的で最初に指定されたURLを表示します。詳細については、{guideLink}を参照してください。", - "xpack.fleet.settings.fleetServerHostsFlyout.agentPolicyCount": "{agentPolicyCount, plural, other {# 件のエージェントポリシー}}", - "xpack.fleet.settings.fleetServerHostsFlyout.agentsCount": "{agentCount, plural, other {# 個のエージェント}}", - "xpack.fleet.settings.fleetServerHostsFlyout.confirmModalText": "{policies}と{agents}が更新されます。このアクションは元に戻せません。続行していいですか?", "xpack.fleet.settings.fleetServerHostsFlyout.description": "エージェントがFleetサーバーに接続するために使用するURLを指定します。複数のURLが存在する場合、Fleetは登録目的で最初に指定されたURLを表示します。Fleetサーバーはデフォルトで8220番ポートを使用します。{link}を参照してください。", "xpack.fleet.settings.logstashInstructions.addPipelineStepDescription": "Logstash構成ディレクトリの{pipelineFile}ファイルを開き、次の構成を追加します。ファイルへのパスを置換します。", "xpack.fleet.settings.logstashInstructions.description": "Elasticエージェントパイプライン構成をLogstashに追加し、Elasticエージェントフレームワークからイベントを受信します。{documentationLink}。", @@ -13384,8 +13380,6 @@ "xpack.fleet.fleetServerFlyout.getStartedTitle": "Fleetサーバーの基本", "xpack.fleet.fleetServerFlyout.installFleetServerTitle": "Fleetサーバーを一元化されたホストにインストール", "xpack.fleet.fleetServerFlyout.title": "Fleetサーバーを追加", - "xpack.fleet.fleetServerHost.requiredError": "Fleetサーバーホストは必須です。", - "xpack.fleet.fleetServerHost.requiresHttpsError": "Fleetサーバーホストの先頭は「https」でなければなりません", "xpack.fleet.fleetServerOnPremRequiredCallout.calloutTitle": "Fleetにエージェントを登録する前に、Fleetサーバーが必要です。", "xpack.fleet.fleetServerOnPremRequiredCallout.guideLink": "FleetおよびElasticエージェントガイド", "xpack.fleet.fleetServerOnPremUnhealthyCallout.addFleetServerButtonLabel": "Fleetサーバーの追加", @@ -13393,7 +13387,6 @@ "xpack.fleet.fleetServerOnPremUnhealthyCallout.guideLink": "FleetおよびElasticエージェントガイド", "xpack.fleet.fleetServerSetup.addFleetServerHostButton": "ホストの追加", "xpack.fleet.fleetServerSetup.addFleetServerHostInputLabel": "Fleetサーバーホスト", - "xpack.fleet.fleetServerSetup.addFleetServerHostInvalidUrlError": "無効なURL", "xpack.fleet.fleetServerSetup.addFleetServerHostStepTitle": "Fleetサーバーホストの追加", "xpack.fleet.fleetServerSetup.addFleetServerHostSuccessTitle": "追加されたFleetサーバーホスト", "xpack.fleet.fleetServerSetup.cloudDeploymentLink": "デプロイを編集", @@ -13594,7 +13587,6 @@ "xpack.fleet.settings.editOutputFlyout.typeInputPlaceholder": "タイプを指定", "xpack.fleet.settings.editOutputFlyout.yamlConfigInputLabel": "詳細YAML構成", "xpack.fleet.settings.editOutputFlyout.yamlConfigInputPlaceholder": "# このYAML設定は、各エージェントポリシーの出力セクションに追加されます。", - "xpack.fleet.settings.fleetServerHostEditButtonLabel": "ホストを編集", "xpack.fleet.settings.fleetServerHostsDifferentPathOrProtocolError": "各URLのプロトコルとパスは同じでなければなりません", "xpack.fleet.settings.fleetServerHostsDuplicateError": "重複するURL", "xpack.fleet.settings.fleetServerHostSectionTitle": "Fleetサーバーホスト", @@ -13606,11 +13598,8 @@ "xpack.fleet.settings.fleetServerHostsFlyout.fleetServerHostsInputPlaceholder": "ホストURLを指定", "xpack.fleet.settings.fleetServerHostsFlyout.saveButton": "設定を保存して適用", "xpack.fleet.settings.fleetServerHostsFlyout.successToastTitle": "設定が保存されました", - "xpack.fleet.settings.fleetServerHostsFlyout.title": "Fleetサーバーホスト", "xpack.fleet.settings.fleetServerHostsFlyout.userGuideLink": "FleetおよびElasticエージェントガイド", - "xpack.fleet.settings.fleetServerHostsPreconfiguredTooltipContent": "Fleet Serverホストは、Fleet外で構成されます。詳細については、Kibana構成を参照してください。", "xpack.fleet.settings.fleetServerHostsRequiredError": "ホストURLは必須です", - "xpack.fleet.settings.fleetServerHostUrlColumnTitle": "ホストURL", "xpack.fleet.settings.fleetSettingsLink": "詳細", "xpack.fleet.settings.fleetUserGuideLink": "FleetおよびElasticエージェントガイド", "xpack.fleet.settings.logstashInstructions.apiKeyStepDescription": "Elasticエージェントの最小限の権限で、LogstashがElasticsearchに出力できるようにすることをお勧めします。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 19bd788256f57..1239010846ed6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12704,7 +12704,6 @@ "xpack.fleet.epmList.verificationWarningCalloutIntroText": "一个或多个已安装的集成包含真实性未知的未签名软件包。详细了解 {learnMoreLink}。", "xpack.fleet.fleetServerCloudRequiredCallout.calloutDescription": "需要提供运行正常的 Fleet 服务器,才能使用 Fleet 注册代理。在 {cloudDeploymentLink} 中启用 Fleet 服务器。有关详细信息,请参阅 {guideLink}。", "xpack.fleet.fleetServerFlyout.generateFleetServerPolicySuccessInstructions": "已生成 Fleet 服务器策略和服务令牌。已在 {hostUrl} 配置主机。您可以在{fleetSettingsLink}中编辑 Fleet 服务器主机。", - "xpack.fleet.fleetServerFlyout.getStartedInstructions": "首先,设置代理将用于访问 Fleet 服务器的公共 IP 或主机名和端口。它默认使用端口 {port}。然后,将自动为您生成策略。", "xpack.fleet.fleetServerFlyout.installFleetServerInstructions": "在集中式主机上安装 Fleet 服务器代理,以便您希望监测的其他主机与其建立连接。在生产环境中,我们建议使用一台或多台专用主机。如需其他指南,请参阅我们的 {installationLink}。", "xpack.fleet.fleetServerFlyout.instructions": "需要提供 Fleet 服务器,才能使用 Fleet 注册代理。按照下面的说明设置 Fleet 服务器。有关详细信息,请参阅{userGuideLink}", "xpack.fleet.fleetServerOnPremRequiredCallout.calloutDescription": "按照下面的说明设置 Fleet 服务器。有关详细信息,请参阅 {guideLink}。", @@ -12784,9 +12783,6 @@ "xpack.fleet.settings.editOutputFlyout.defaultOutputSwitchLabel": "将此输出设为 {boldAgentIntegrations} 的默认值。", "xpack.fleet.settings.editOutputFlyout.logstashHostsInputDescription": "指定代理将用于连接到 Logstash 的地址。{guideLink}。", "xpack.fleet.settings.fleetServerHostSectionSubtitle": "指定代理用于连接 Fleet 服务器的 URL。如果存在多个 URL,Fleet 将显示提供的第一个 URL 用于注册。有关详细信息,请参阅 {guideLink}。", - "xpack.fleet.settings.fleetServerHostsFlyout.agentPolicyCount": "{agentPolicyCount, plural, other {# 个代理策略}}", - "xpack.fleet.settings.fleetServerHostsFlyout.agentsCount": "{agentCount, plural, other {# 个代理}}", - "xpack.fleet.settings.fleetServerHostsFlyout.confirmModalText": "此操作将更新 {policies} 和 {agents}。此操作无法撤消。是否确定要继续?", "xpack.fleet.settings.fleetServerHostsFlyout.description": "指定代理用于连接 Fleet 服务器的 URL。如果多个 URL 存在,Fleet 显示提供的第一个 URL 用于注册。Fleet 服务器默认使用端口 8220。请参阅 {link}。", "xpack.fleet.settings.logstashInstructions.addPipelineStepDescription": "在 Logstash 配置目录中,打开 {pipelineFile} 文件并添加以下配置。替换您文件的路径。", "xpack.fleet.settings.logstashInstructions.description": "将 Elastic 代理管道配置添加到 Logstash,以从 Elastic 代理框架接收事件。{documentationLink}。", @@ -13404,8 +13400,6 @@ "xpack.fleet.fleetServerFlyout.getStartedTitle": "开始使用 Fleet 服务器", "xpack.fleet.fleetServerFlyout.installFleetServerTitle": "将 Fleet 服务器安装到集中式主机", "xpack.fleet.fleetServerFlyout.title": "添加 Fleet 服务器", - "xpack.fleet.fleetServerHost.requiredError": "Fleet 服务器主机必填。", - "xpack.fleet.fleetServerHost.requiresHttpsError": "Fleet 服务器主机必须以“https”开头", "xpack.fleet.fleetServerOnPremRequiredCallout.calloutTitle": "在使用 Fleet 注册代理之前,需要提供 Fleet 服务器。", "xpack.fleet.fleetServerOnPremRequiredCallout.guideLink": "Fleet 和 Elastic 代理指南", "xpack.fleet.fleetServerOnPremUnhealthyCallout.addFleetServerButtonLabel": "添加 Fleet 服务器", @@ -13413,7 +13407,6 @@ "xpack.fleet.fleetServerOnPremUnhealthyCallout.guideLink": "Fleet 和 Elastic 代理指南", "xpack.fleet.fleetServerSetup.addFleetServerHostButton": "添加主机", "xpack.fleet.fleetServerSetup.addFleetServerHostInputLabel": "Fleet 服务器主机", - "xpack.fleet.fleetServerSetup.addFleetServerHostInvalidUrlError": "URL 无效", "xpack.fleet.fleetServerSetup.addFleetServerHostStepTitle": "添加您的 Fleet 服务器主机", "xpack.fleet.fleetServerSetup.addFleetServerHostSuccessTitle": "已添加 Fleet 服务器主机", "xpack.fleet.fleetServerSetup.cloudDeploymentLink": "编辑部署", @@ -13614,7 +13607,6 @@ "xpack.fleet.settings.editOutputFlyout.typeInputPlaceholder": "指定类型", "xpack.fleet.settings.editOutputFlyout.yamlConfigInputLabel": "高级 YAML 配置", "xpack.fleet.settings.editOutputFlyout.yamlConfigInputPlaceholder": "此处的 # 个 YAML 设置将添加到每个代理策略的输出部分。", - "xpack.fleet.settings.fleetServerHostEditButtonLabel": "编辑主机", "xpack.fleet.settings.fleetServerHostsDifferentPathOrProtocolError": "对于每个 URL,协议和路径必须相同", "xpack.fleet.settings.fleetServerHostsDuplicateError": "复制 URL", "xpack.fleet.settings.fleetServerHostSectionTitle": "Fleet 服务器主机", @@ -13626,11 +13618,8 @@ "xpack.fleet.settings.fleetServerHostsFlyout.fleetServerHostsInputPlaceholder": "指定主机 URL", "xpack.fleet.settings.fleetServerHostsFlyout.saveButton": "保存并应用设置", "xpack.fleet.settings.fleetServerHostsFlyout.successToastTitle": "设置已保存", - "xpack.fleet.settings.fleetServerHostsFlyout.title": "Fleet 服务器主机", "xpack.fleet.settings.fleetServerHostsFlyout.userGuideLink": "Fleet 和 Elastic 代理指南", - "xpack.fleet.settings.fleetServerHostsPreconfiguredTooltipContent": "Fleet 服务器主机在 Fleet 以外进行配置。请参阅 Kibana 配置了解详情。", "xpack.fleet.settings.fleetServerHostsRequiredError": "主机 URL 必填", - "xpack.fleet.settings.fleetServerHostUrlColumnTitle": "主机 URL", "xpack.fleet.settings.fleetSettingsLink": "了解详情", "xpack.fleet.settings.fleetUserGuideLink": "Fleet 和 Elastic 代理指南", "xpack.fleet.settings.logstashInstructions.apiKeyStepDescription": "建议授权 Logstash 以 Elastic 代理的最低权限输出到 Elasticsearch。", diff --git a/x-pack/test/functional/es_archives/fleet/agents/data.json b/x-pack/test/functional/es_archives/fleet/agents/data.json index 8708710321088..14f3704cdb623 100644 --- a/x-pack/test/functional/es_archives/fleet/agents/data.json +++ b/x-pack/test/functional/es_archives/fleet/agents/data.json @@ -89,6 +89,20 @@ } } +{ + "type": "doc", + "value": { + "id": "fleet-server-host-1", + "index": ".fleet-fleet-server-host", + "source": { + "id": "test-default-123", + "name": "Default", + "is_default": true, + "host_urls": ["https://test.fr:8080", "https://test.fr:8081"] + } + } +} + { "type": "doc", "value": {