From 8293d77875dc538709c496dad8515f772a47d925 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Wed, 29 Nov 2023 15:45:10 +0100 Subject: [PATCH 01/10] output health --- .../plugins/fleet/common/constants/output.ts | 2 + .../plugins/fleet/common/constants/routes.ts | 1 + .../plugins/fleet/common/services/routes.ts | 2 + .../fleet/common/types/rest_spec/output.ts | 5 ++ .../components/edit_output_flyout/index.tsx | 4 ++ .../edit_output_flyout/output_health.tsx | 70 +++++++++++++++++++ .../fleet/public/hooks/use_request/outputs.ts | 10 +++ .../plugins/fleet/server/constants/index.ts | 2 + .../fleet/server/routes/output/handler.ts | 16 +++++ .../fleet/server/routes/output/index.ts | 17 +++++ .../plugins/fleet/server/services/output.ts | 26 +++++++ .../fleet/server/types/rest_spec/output.ts | 6 ++ 12 files changed, 161 insertions(+) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx diff --git a/x-pack/plugins/fleet/common/constants/output.ts b/x-pack/plugins/fleet/common/constants/output.ts index fdf2075c541a3..376e2c363225f 100644 --- a/x-pack/plugins/fleet/common/constants/output.ts +++ b/x-pack/plugins/fleet/common/constants/output.ts @@ -119,3 +119,5 @@ export const kafkaSupportedVersions = [ '2.5.1', '2.6.0', ]; + +export const OUTPUT_HEALTH_DATA_STREAM = 'logs-fleet_server.output_health-default'; diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index 3a5df3768ae96..3f3d3d932c999 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -94,6 +94,7 @@ export const OUTPUT_API_ROUTES = { UPDATE_PATTERN: `${API_ROOT}/outputs/{outputId}`, DELETE_PATTERN: `${API_ROOT}/outputs/{outputId}`, CREATE_PATTERN: `${API_ROOT}/outputs`, + GET_OUTPUT_HEALTH_PATTERN: `${API_ROOT}/outputs/{outputId}/health`, LOGSTASH_API_KEY_PATTERN: `${API_ROOT}/logstash_api_keys`, }; diff --git a/x-pack/plugins/fleet/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts index 5aeb25c0e90bf..0ea2cb2bc0de9 100644 --- a/x-pack/plugins/fleet/common/services/routes.ts +++ b/x-pack/plugins/fleet/common/services/routes.ts @@ -248,6 +248,8 @@ export const outputRoutesService = { OUTPUT_API_ROUTES.DELETE_PATTERN.replace('{outputId}', outputId), getCreatePath: () => OUTPUT_API_ROUTES.CREATE_PATTERN, getCreateLogstashApiKeyPath: () => OUTPUT_API_ROUTES.LOGSTASH_API_KEY_PATTERN, + getOutputHealthPath: (outputId: string) => + OUTPUT_API_ROUTES.GET_OUTPUT_HEALTH_PATTERN.replace('{outputId}', outputId), }; export const fleetProxiesRoutesService = { diff --git a/x-pack/plugins/fleet/common/types/rest_spec/output.ts b/x-pack/plugins/fleet/common/types/rest_spec/output.ts index 2de6be2b47458..3f97ce22b0b9d 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/output.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/output.ts @@ -43,3 +43,8 @@ export type GetOutputsResponse = ListResult; export interface PostLogstashApiKeyResponse { api_key: string; } + +export interface GetOutputHealthResponse { + state: string; + message: string; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx index 0e742876af82d..7ceef20111042 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx @@ -55,6 +55,7 @@ import { useOutputForm } from './use_output_form'; import { EncryptionKeyRequiredCallout } from './encryption_key_required_callout'; import { AdvancedOptionsSection } from './advanced_options_section'; import { OutputFormRemoteEsSection } from './output_form_remote_es'; +import { OutputHealth } from './output_health'; export interface EditOutputFlyoutProps { output?: Output; @@ -576,6 +577,9 @@ export const EditOutputFlyout: React.FunctionComponent = + {output?.id && output.type === 'remote_elasticsearch' ? ( + + ) : null} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx new file mode 100644 index 0000000000000..20ce59830a226 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.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 { EuiCallOut } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import React, { useCallback, useEffect, useState } from 'react'; + +import type { GetOutputHealthResponse } from '../../../../../../../common/types'; + +import { sendGetOutputHealth, useStartServices } from '../../../../hooks'; +import type { Output } from '../../../../types'; + +interface Props { + output: Output; +} +const REFRESH_INTERVAL_MS = 5000; + +export const OutputHealth: React.FunctionComponent = ({ output }) => { + const { notifications } = useStartServices(); + const [outputHealth, setOutputHealth] = useState(); + const fetchData = useCallback(async () => { + try { + const response = await sendGetOutputHealth(output.id); + if (response.error) { + throw response.error; + } + setOutputHealth(response.data); + } catch (error) { + notifications.toasts.addError(error, { + title: i18n.translate('xpack.fleet.output.errorFetchingOutputHealth', { + defaultMessage: 'Error fetching output state', + }), + }); + } + }, [output.id, notifications.toasts]); + + // Send request to get output health + useEffect(() => { + fetchData(); + const interval = setInterval(() => { + fetchData(); + }, REFRESH_INTERVAL_MS); + + return () => clearInterval(interval); + }, [fetchData]); + + return outputHealth?.state === 'DEGRADED' ? ( + +

+ {i18n.translate('xpack.fleet.output.calloutText', { + defaultMessage: 'Unable to connect to "{name}" at {host}.', + values: { + name: output.name, + host: output.hosts?.join(',') ?? '', + }, + })} +

{' '} +

+ {i18n.translate('xpack.fleet.output.calloutPromptText', { + defaultMessage: 'Please check the details are correct.', + })} +

+
+ ) : null; +}; diff --git a/x-pack/plugins/fleet/public/hooks/use_request/outputs.ts b/x-pack/plugins/fleet/public/hooks/use_request/outputs.ts index e0f1fc8d205c7..8bd19d1860931 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/outputs.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/outputs.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { GetOutputHealthResponse } from '../../../common/types'; + import { outputRoutesService } from '../../services'; import type { PutOutputRequest, @@ -65,3 +67,11 @@ export function sendDeleteOutput(outputId: string) { version: API_VERSIONS.public.v1, }); } + +export function sendGetOutputHealth(outputId: string) { + return sendRequest({ + method: 'get', + path: outputRoutesService.getOutputHealthPath(outputId), + version: API_VERSIONS.public.v1, + }); +} diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index 7a03f7c33ab3d..4053dff4c3b96 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -81,6 +81,8 @@ export { // secrets SECRETS_ENDPOINT_PATH, SECRETS_MINIMUM_FLEET_SERVER_VERSION, + // outputs + OUTPUT_HEALTH_DATA_STREAM, type PrivilegeMapObject, } from '../../common/constants'; diff --git a/x-pack/plugins/fleet/server/routes/output/handler.ts b/x-pack/plugins/fleet/server/routes/output/handler.ts index dc6682fe82727..a63838e7a2063 100644 --- a/x-pack/plugins/fleet/server/routes/output/handler.ts +++ b/x-pack/plugins/fleet/server/routes/output/handler.ts @@ -16,6 +16,7 @@ import { outputType } from '../../../common/constants'; import type { DeleteOutputRequestSchema, + GetLatestOutputHealthRequestSchema, GetOneOutputRequestSchema, PostOutputRequestSchema, PutOutputRequestSchema, @@ -207,3 +208,18 @@ export const postLogstashApiKeyHandler: RequestHandler = async (context, request return defaultFleetErrorHandler({ error, response }); } }; + +export const getLatestOutputHealth: RequestHandler< + TypeOf +> = async (context, request, response) => { + const esClient = (await context.core).elasticsearch.client.asCurrentUser; + try { + const outputHealth = await outputService.getLatestOutputHealth( + esClient, + request.params.outputId + ); + return response.ok({ body: outputHealth }); + } catch (error) { + return defaultFleetErrorHandler({ error, response }); + } +}; diff --git a/x-pack/plugins/fleet/server/routes/output/index.ts b/x-pack/plugins/fleet/server/routes/output/index.ts index 3b769118da5a3..3a566471aa4d8 100644 --- a/x-pack/plugins/fleet/server/routes/output/index.ts +++ b/x-pack/plugins/fleet/server/routes/output/index.ts @@ -12,6 +12,7 @@ import { API_VERSIONS } from '../../../common/constants'; import { OUTPUT_API_ROUTES } from '../../constants'; import { DeleteOutputRequestSchema, + GetLatestOutputHealthRequestSchema, GetOneOutputRequestSchema, GetOutputsRequestSchema, PostOutputRequestSchema, @@ -25,6 +26,7 @@ import { postOutputHandler, putOutputHandler, postLogstashApiKeyHandler, + getLatestOutputHealth, } from './handler'; export const registerRoutes = (router: FleetAuthzRouter) => { @@ -115,4 +117,19 @@ export const registerRoutes = (router: FleetAuthzRouter) => { }, postLogstashApiKeyHandler ); + + router.versioned + .get({ + path: OUTPUT_API_ROUTES.GET_OUTPUT_HEALTH_PATTERN, + fleetAuthz: { + fleet: { all: true }, + }, + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { request: GetLatestOutputHealthRequestSchema }, + }, + getLatestOutputHealth + ); }; diff --git a/x-pack/plugins/fleet/server/services/output.ts b/x-pack/plugins/fleet/server/services/output.ts index 0cb1099b58ebb..65fcd1c060e8b 100644 --- a/x-pack/plugins/fleet/server/services/output.ts +++ b/x-pack/plugins/fleet/server/services/output.ts @@ -33,6 +33,7 @@ import { DEFAULT_OUTPUT, DEFAULT_OUTPUT_ID, OUTPUT_SAVED_OBJECT_TYPE, + OUTPUT_HEALTH_DATA_STREAM, } from '../constants'; import { SO_SEARCH_LIMIT, @@ -952,6 +953,31 @@ class OutputService { } } } + + async getLatestOutputHealth(esClient: ElasticsearchClient, id: string): Promise { + const response = await esClient.search({ + index: OUTPUT_HEALTH_DATA_STREAM, + query: { bool: { filter: { term: { output: id } } } }, + sort: { '@timestamp': 'desc' }, + size: 1, + }); + if (response.hits.hits.length === 0) { + return { + state: 'UNKOWN', + message: '', + }; + } + const latestHit = response.hits.hits[0]._source as any; + return { + state: latestHit.state, + message: latestHit.message ?? '', + }; + } +} + +interface OutputHealth { + state: string; + message: string; } export const outputService = new OutputService(); diff --git a/x-pack/plugins/fleet/server/types/rest_spec/output.ts b/x-pack/plugins/fleet/server/types/rest_spec/output.ts index c6c7ba89bc0a6..8a2d6babbd774 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/output.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/output.ts @@ -33,3 +33,9 @@ export const PutOutputRequestSchema = { }), body: UpdateOutputSchema, }; + +export const GetLatestOutputHealthRequestSchema = { + params: schema.object({ + outputId: schema.string(), + }), +}; From ecb23c7fa9443bbaaae90875d2bb2042ed42a373 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Thu, 30 Nov 2023 13:36:48 +0100 Subject: [PATCH 02/10] added badge to output list --- .../edit_output_flyout/output_health.tsx | 78 ++++++++++++++----- .../components/outputs_table/index.tsx | 22 ++++-- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx index 20ce59830a226..6cc1c5090de95 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import { EuiCallOut } from '@elastic/eui'; +import { EuiBadge, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import React, { useCallback, useEffect, useState } from 'react'; import type { GetOutputHealthResponse } from '../../../../../../../common/types'; @@ -17,10 +18,11 @@ import type { Output } from '../../../../types'; interface Props { output: Output; + showBadge?: boolean; } const REFRESH_INTERVAL_MS = 5000; -export const OutputHealth: React.FunctionComponent = ({ output }) => { +export const OutputHealth: React.FunctionComponent = ({ output, showBadge }) => { const { notifications } = useStartServices(); const [outputHealth, setOutputHealth] = useState(); const fetchData = useCallback(async () => { @@ -49,22 +51,58 @@ export const OutputHealth: React.FunctionComponent = ({ output }) => { return () => clearInterval(interval); }, [fetchData]); - return outputHealth?.state === 'DEGRADED' ? ( - -

- {i18n.translate('xpack.fleet.output.calloutText', { - defaultMessage: 'Unable to connect to "{name}" at {host}.', - values: { - name: output.name, - host: output.hosts?.join(',') ?? '', - }, - })} -

{' '} -

- {i18n.translate('xpack.fleet.output.calloutPromptText', { - defaultMessage: 'Please check the details are correct.', - })} -

-
- ) : null; + const EditOutputStatus: { [status: string]: JSX.Element | null } = { + DEGRADED: ( + +

+ {i18n.translate('xpack.fleet.output.calloutText', { + defaultMessage: 'Unable to connect to "{name}" at {host}.', + values: { + name: output.name, + host: output.hosts?.join(',') ?? '', + }, + })} +

{' '} +

+ {i18n.translate('xpack.fleet.output.calloutPromptText', { + defaultMessage: 'Please check the details are correct.', + })} +

+
+ ), + HEALTHY: ( + +

+ {i18n.translate('xpack.fleet.output.successCalloutText', { + defaultMessage: 'Connection with remote output established.', + })} +

+
+ ), + }; + + const OutputStatusBadge: { [status: string]: JSX.Element | null } = { + DEGRADED: ( + + + + ), + HEALTHY: ( + + + + ), + }; + + return outputHealth?.state + ? showBadge + ? OutputStatusBadge[outputHealth.state] || null + : EditOutputStatus[outputHealth.state] || null + : null; }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/outputs_table/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/outputs_table/index.tsx index fe05e00f795d7..dc27e83d881c1 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/outputs_table/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/outputs_table/index.tsx @@ -14,6 +14,8 @@ import { i18n } from '@kbn/i18n'; import { useLink } from '../../../../hooks'; import type { Output } from '../../../../types'; +import { OutputHealth } from '../edit_output_flyout/output_health'; + import { DefaultBadges } from './badges'; export interface OutputsTableProps { @@ -76,14 +78,14 @@ export const OutputsTable: React.FunctionComponent = ({
), width: '288px', - name: i18n.translate('xpack.fleet.settings.outputsTable.nameColomnTitle', { + name: i18n.translate('xpack.fleet.settings.outputsTable.nameColumnTitle', { defaultMessage: 'Name', }), }, { width: '172px', render: (output: Output) => displayOutputType(output.type), - name: i18n.translate('xpack.fleet.settings.outputsTable.typeColomnTitle', { + name: i18n.translate('xpack.fleet.settings.outputsTable.typeColumnTitle', { defaultMessage: 'Type', }), }, @@ -100,14 +102,24 @@ export const OutputsTable: React.FunctionComponent = ({ ))} ), - name: i18n.translate('xpack.fleet.settings.outputsTable.hostColomnTitle', { + name: i18n.translate('xpack.fleet.settings.outputsTable.hostColumnTitle', { defaultMessage: 'Hosts', }), }, + { + render: (output: Output) => { + return output?.id && output.type === 'remote_elasticsearch' ? ( + + ) : null; + }, + name: i18n.translate('xpack.fleet.settings.outputsTable.statusColumnTitle', { + defaultMessage: 'Status', + }), + }, { render: (output: Output) => , width: '200px', - name: i18n.translate('xpack.fleet.settings.outputSection.defaultColomnTitle', { + name: i18n.translate('xpack.fleet.settings.outputSection.defaultColumnTitle', { defaultMessage: 'Default', }), }, @@ -145,7 +157,7 @@ export const OutputsTable: React.FunctionComponent = ({ ); }, - name: i18n.translate('xpack.fleet.settings.outputSection.actionsColomnTitle', { + name: i18n.translate('xpack.fleet.settings.outputSection.actionsColumnTitle', { defaultMessage: 'Actions', }), }, From 99322ed0ccc36fafefa411a0340ee0e794f4efc2 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Thu, 30 Nov 2023 13:47:40 +0100 Subject: [PATCH 03/10] use react query to fetch output health --- .../edit_output_flyout/output_health.tsx | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx index 6cc1c5090de95..5e56d7e771ab0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx @@ -9,7 +9,8 @@ import { EuiBadge, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; import type { GetOutputHealthResponse } from '../../../../../../../common/types'; @@ -20,36 +21,28 @@ interface Props { output: Output; showBadge?: boolean; } -const REFRESH_INTERVAL_MS = 5000; +const REFRESH_INTERVAL_MS = 10000; export const OutputHealth: React.FunctionComponent = ({ output, showBadge }) => { const { notifications } = useStartServices(); const [outputHealth, setOutputHealth] = useState(); - const fetchData = useCallback(async () => { - try { - const response = await sendGetOutputHealth(output.id); - if (response.error) { - throw response.error; - } - setOutputHealth(response.data); - } catch (error) { - notifications.toasts.addError(error, { + + const { data: outputHealthResponse } = useQuery( + ['outputHealth', output.id], + () => sendGetOutputHealth(output.id), + { refetchInterval: REFRESH_INTERVAL_MS } + ); + useEffect(() => { + if (outputHealthResponse?.error) { + notifications.toasts.addError(outputHealthResponse?.error, { title: i18n.translate('xpack.fleet.output.errorFetchingOutputHealth', { defaultMessage: 'Error fetching output state', }), }); + return; } - }, [output.id, notifications.toasts]); - - // Send request to get output health - useEffect(() => { - fetchData(); - const interval = setInterval(() => { - fetchData(); - }, REFRESH_INTERVAL_MS); - - return () => clearInterval(interval); - }, [fetchData]); + setOutputHealth(outputHealthResponse?.data); + }, [outputHealthResponse, notifications.toasts]); const EditOutputStatus: { [status: string]: JSX.Element | null } = { DEGRADED: ( From 57fa51eca6a5afcc1ce62be60171eb59dd695411 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Thu, 30 Nov 2023 13:48:27 +0100 Subject: [PATCH 04/10] reset output health if error --- .../settings/components/edit_output_flyout/output_health.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx index 5e56d7e771ab0..b983013376fd2 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx @@ -39,7 +39,6 @@ export const OutputHealth: React.FunctionComponent = ({ output, showBadge defaultMessage: 'Error fetching output state', }), }); - return; } setOutputHealth(outputHealthResponse?.data); }, [outputHealthResponse, notifications.toasts]); From 1e0f352bce551033ce8c250e82154e755da97517 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Thu, 30 Nov 2023 14:47:43 +0100 Subject: [PATCH 05/10] showing last reported time --- .../fleet/common/types/rest_spec/output.ts | 1 + .../edit_output_flyout/output_health.tsx | 35 +++++++++++++++---- .../plugins/fleet/server/services/output.ts | 3 ++ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/rest_spec/output.ts b/x-pack/plugins/fleet/common/types/rest_spec/output.ts index 3f97ce22b0b9d..101fff9a2c13e 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/output.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/output.ts @@ -47,4 +47,5 @@ export interface PostLogstashApiKeyResponse { export interface GetOutputHealthResponse { state: string; message: string; + timestamp: string; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx index b983013376fd2..0c401722e62d4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx @@ -5,10 +5,10 @@ * 2.0. */ -import { EuiBadge, EuiCallOut } from '@elastic/eui'; +import { EuiBadge, EuiCallOut, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; +import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react'; import React, { useEffect, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; @@ -92,9 +92,30 @@ export const OutputHealth: React.FunctionComponent = ({ output, showBadge ), }; - return outputHealth?.state - ? showBadge - ? OutputStatusBadge[outputHealth.state] || null - : EditOutputStatus[outputHealth.state] || null - : null; + const msLastTimestamp = new Date(outputHealth?.timestamp || 0).getTime(); + const lastTimestampText = msLastTimestamp ? ( + <> + , + }} + /> + + ) : null; + + const outputBadge = (outputHealth?.state && OutputStatusBadge[outputHealth?.state]) || null; + + return showBadge ? ( + lastTimestampText && outputHealth?.state ? ( + + <>{outputBadge} + + ) : ( + outputBadge + ) + ) : ( + (outputHealth?.state && EditOutputStatus[outputHealth.state]) || null + ); }; diff --git a/x-pack/plugins/fleet/server/services/output.ts b/x-pack/plugins/fleet/server/services/output.ts index 65fcd1c060e8b..05280b3cb2d38 100644 --- a/x-pack/plugins/fleet/server/services/output.ts +++ b/x-pack/plugins/fleet/server/services/output.ts @@ -965,12 +965,14 @@ class OutputService { return { state: 'UNKOWN', message: '', + timestamp: '', }; } const latestHit = response.hits.hits[0]._source as any; return { state: latestHit.state, message: latestHit.message ?? '', + timestamp: latestHit['@timestamp'], }; } } @@ -978,6 +980,7 @@ class OutputService { interface OutputHealth { state: string; message: string; + timestamp: string; } export const outputService = new OutputService(); From fb3f4779d5d6aec7d6fc275f5f0b18c5486e0fe5 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Thu, 30 Nov 2023 15:18:05 +0100 Subject: [PATCH 06/10] renamed translations --- x-pack/plugins/translations/translations/fr-FR.json | 10 +++++----- x-pack/plugins/translations/translations/ja-JP.json | 10 +++++----- x-pack/plugins/translations/translations/zh-CN.json | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 2a45559b22b5e..10eb028cd1032 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -18229,17 +18229,17 @@ "xpack.fleet.settings.outputForm.sslKeyRequiredErrorMessage": "Clé SSL requise", "xpack.fleet.settings.outputs.defaultMonitoringOutputBadgeTitle": "Monitoring des agents", "xpack.fleet.settings.outputs.defaultOutputBadgeTitle": "Intégrations d’agent", - "xpack.fleet.settings.outputSection.actionsColomnTitle": "Actions", - "xpack.fleet.settings.outputSection.defaultColomnTitle": "Par défaut", + "xpack.fleet.settings.outputSection.actionsColumnTitle": "Actions", + "xpack.fleet.settings.outputSection.defaultColumnTitle": "Par défaut", "xpack.fleet.settings.outputSection.deleteButtonTitle": "Supprimer", "xpack.fleet.settings.outputSection.editButtonTitle": "Modifier", "xpack.fleet.settings.outputSectionSubtitle": "Indiquez l’emplacement vers lequel les agents doivent envoyer les données.", "xpack.fleet.settings.outputSectionTitle": "Sorties", "xpack.fleet.settings.outputsTable.elasticsearchTypeLabel": "Elasticsearch", - "xpack.fleet.settings.outputsTable.hostColomnTitle": "Hôtes", + "xpack.fleet.settings.outputsTable.hostColumnTitle": "Hôtes", "xpack.fleet.settings.outputsTable.managedTooltip": "Cette sortie est gérée en dehors de Fleet.", - "xpack.fleet.settings.outputsTable.nameColomnTitle": "Nom", - "xpack.fleet.settings.outputsTable.typeColomnTitle": "Type", + "xpack.fleet.settings.outputsTable.nameColumnTitle": "Nom", + "xpack.fleet.settings.outputsTable.typeColumnTitle": "Type", "xpack.fleet.settings.sortHandle": "Trier par identification d'hôte", "xpack.fleet.settings.updateDownloadSourceModal.confirmModalTitle": "Enregistrer et déployer les modifications ?", "xpack.fleet.settings.updateOutput.confirmModalTitle": "Enregistrer et déployer les modifications ?", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5357b3a0908ad..ac8f1b387d5e1 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -18242,17 +18242,17 @@ "xpack.fleet.settings.outputForm.sslKeyRequiredErrorMessage": "SSL鍵が必要です", "xpack.fleet.settings.outputs.defaultMonitoringOutputBadgeTitle": "アラート監視", "xpack.fleet.settings.outputs.defaultOutputBadgeTitle": "エージェント統合", - "xpack.fleet.settings.outputSection.actionsColomnTitle": "アクション", - "xpack.fleet.settings.outputSection.defaultColomnTitle": "デフォルト", + "xpack.fleet.settings.outputSection.actionsColumnTitle": "アクション", + "xpack.fleet.settings.outputSection.defaultColumnTitle": "デフォルト", "xpack.fleet.settings.outputSection.deleteButtonTitle": "削除", "xpack.fleet.settings.outputSection.editButtonTitle": "編集", "xpack.fleet.settings.outputSectionSubtitle": "エージェントがデータを送信する場合を指定します。", "xpack.fleet.settings.outputSectionTitle": "アウトプット", "xpack.fleet.settings.outputsTable.elasticsearchTypeLabel": "Elasticsearch", - "xpack.fleet.settings.outputsTable.hostColomnTitle": "ホスト", + "xpack.fleet.settings.outputsTable.hostColumnTitle": "ホスト", "xpack.fleet.settings.outputsTable.managedTooltip": "この出力はFleet外で管理されます。", - "xpack.fleet.settings.outputsTable.nameColomnTitle": "名前", - "xpack.fleet.settings.outputsTable.typeColomnTitle": "型", + "xpack.fleet.settings.outputsTable.nameColumnTitle": "名前", + "xpack.fleet.settings.outputsTable.typeColumnTitle": "型", "xpack.fleet.settings.sortHandle": "ホストハンドルの並べ替え", "xpack.fleet.settings.updateDownloadSourceModal.confirmModalTitle": "変更を保存してデプロイしますか?", "xpack.fleet.settings.updateOutput.confirmModalTitle": "変更を保存してデプロイしますか?", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 916c2eba4d8e9..d0af7e822830a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18242,17 +18242,17 @@ "xpack.fleet.settings.outputForm.sslKeyRequiredErrorMessage": "SSL 密钥必填", "xpack.fleet.settings.outputs.defaultMonitoringOutputBadgeTitle": "代理监测", "xpack.fleet.settings.outputs.defaultOutputBadgeTitle": "代理集成", - "xpack.fleet.settings.outputSection.actionsColomnTitle": "操作", - "xpack.fleet.settings.outputSection.defaultColomnTitle": "默认", + "xpack.fleet.settings.outputSection.actionsColumnTitle": "操作", + "xpack.fleet.settings.outputSection.defaultColumnTitle": "默认", "xpack.fleet.settings.outputSection.deleteButtonTitle": "删除", "xpack.fleet.settings.outputSection.editButtonTitle": "编辑", "xpack.fleet.settings.outputSectionSubtitle": "指定代理将发送数据的位置。", "xpack.fleet.settings.outputSectionTitle": "输出", "xpack.fleet.settings.outputsTable.elasticsearchTypeLabel": "Elasticsearch", - "xpack.fleet.settings.outputsTable.hostColomnTitle": "主机", + "xpack.fleet.settings.outputsTable.hostColumnTitle": "主机", "xpack.fleet.settings.outputsTable.managedTooltip": "此输出在 Fleet 外部进行管理。", - "xpack.fleet.settings.outputsTable.nameColomnTitle": "名称", - "xpack.fleet.settings.outputsTable.typeColomnTitle": "类型", + "xpack.fleet.settings.outputsTable.nameColumnTitle": "名称", + "xpack.fleet.settings.outputsTable.typeColumnTitle": "类型", "xpack.fleet.settings.sortHandle": "排序主机手柄", "xpack.fleet.settings.updateDownloadSourceModal.confirmModalTitle": "保存并部署更改?", "xpack.fleet.settings.updateOutput.confirmModalTitle": "保存并部署更改?", From c3e3e8dcbf7f90046795e112eb8d726231ef126c Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Thu, 30 Nov 2023 15:50:21 +0100 Subject: [PATCH 07/10] added api tests --- .../fleet/server/services/output.test.ts | 42 +++++++++++++++ .../apis/outputs/crud.ts | 51 ++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/server/services/output.test.ts b/x-pack/plugins/fleet/server/services/output.test.ts index 03d43b130c4ea..6de778e9fd48a 100644 --- a/x-pack/plugins/fleet/server/services/output.test.ts +++ b/x-pack/plugins/fleet/server/services/output.test.ts @@ -1679,4 +1679,46 @@ describe('Output Service', () => { expect(hosts).toEqual(['http://localhost:9200']); }); }); + + describe('getLatestOutputHealth', () => { + it('should return unkown state if no hits', async () => { + esClientMock.search.mockResolvedValue({ + hits: { + hits: [], + }, + } as any); + + const response = await outputService.getLatestOutputHealth(esClientMock, 'id'); + + expect(response).toEqual({ + state: 'UNKOWN', + message: '', + timestamp: '', + }); + }); + + it('should return state from hits', async () => { + esClientMock.search.mockResolvedValue({ + hits: { + hits: [ + { + _source: { + state: 'DEGRADED', + message: 'connection error', + '@timestamp': '2023-11-30T14:25:31Z', + }, + }, + ], + }, + } as any); + + const response = await outputService.getLatestOutputHealth(esClientMock, 'id'); + + expect(response).toEqual({ + state: 'DEGRADED', + message: 'connection error', + timestamp: '2023-11-30T14:25:31Z', + }); + }); + }); }); diff --git a/x-pack/test/fleet_api_integration/apis/outputs/crud.ts b/x-pack/test/fleet_api_integration/apis/outputs/crud.ts index bd44a6be9427e..c015f919ce425 100644 --- a/x-pack/test/fleet_api_integration/apis/outputs/crud.ts +++ b/x-pack/test/fleet_api_integration/apis/outputs/crud.ts @@ -6,7 +6,10 @@ */ import expect from '@kbn/expect'; -import { GLOBAL_SETTINGS_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common/constants'; +import { + GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + OUTPUT_HEALTH_DATA_STREAM, +} from '@kbn/fleet-plugin/common/constants'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; import { setupFleetAndAgents } from '../agents/services'; @@ -189,6 +192,52 @@ export default function (providerContext: FtrProviderContext) { }); }); + describe('GET /outputs/{outputId}/health', () => { + before(async () => { + await es.index({ + refresh: 'wait_for', + index: OUTPUT_HEALTH_DATA_STREAM, + document: { + state: 'HEALTHY', + message: '', + '@timestamp': Date.parse('2023-11-29T14:25:31Z'), + output: defaultOutputId, + }, + }); + + await es.index({ + refresh: 'wait_for', + index: OUTPUT_HEALTH_DATA_STREAM, + document: { + state: 'DEGRADED', + message: 'connection error', + '@timestamp': Date.parse('2023-11-30T14:25:31Z'), + output: defaultOutputId, + }, + }); + + await es.index({ + refresh: 'wait_for', + index: OUTPUT_HEALTH_DATA_STREAM, + document: { + state: 'HEALTHY', + message: '', + '@timestamp': Date.parse('2023-11-31T14:25:31Z'), + output: 'remote2', + }, + }); + }); + it('should allow return the latest output health', async () => { + const { body: outputHealth } = await supertest + .get(`/api/fleet/outputs/${defaultOutputId}/health`) + .expect(200); + + expect(outputHealth.state).to.equal('DEGRADED'); + expect(outputHealth.message).to.equal('connection error'); + expect(outputHealth.timestamp).not.to.be.empty(); + }); + }); + describe('PUT /outputs/{outputId}', () => { it('should explicitly set port on ES hosts', async function () { await supertest From ee5caaa1ee13b9a717bfac2c06358a5a40789cbf Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Thu, 30 Nov 2023 16:53:04 +0100 Subject: [PATCH 08/10] added ui tests --- .../edit_output_flyout/output_health.test.tsx | 143 ++++++++++++++++++ .../edit_output_flyout/output_health.tsx | 24 ++- 2 files changed, 162 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.test.tsx diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.test.tsx new file mode 100644 index 0000000000000..7aa29322229db --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.test.tsx @@ -0,0 +1,143 @@ +/* + * 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 { waitFor } from '@testing-library/react'; + +import type { Output } from '../../../../types'; +import { createFleetTestRendererMock } from '../../../../../../mock'; + +import { sendGetOutputHealth, useStartServices } from '../../../../hooks'; + +import { OutputHealth } from './output_health'; + +jest.mock('../../../../hooks', () => { + return { + ...jest.requireActual('../../../../hooks'), + useStartServices: jest.fn(), + sendGetOutputHealth: jest.fn(), + }; +}); + +jest.mock('@elastic/eui', () => { + return { + ...jest.requireActual('@elastic/eui'), + EuiToolTip: (props: any) => ( +
+ {props.children} +
+ ), + }; +}); + +const mockUseStartServices = useStartServices as jest.Mock; + +const mockSendGetOutputHealth = sendGetOutputHealth as jest.Mock; + +describe('OutputHealth', () => { + function render(output: Output, showBadge?: boolean) { + const renderer = createFleetTestRendererMock(); + + const utils = renderer.render(); + + return { utils }; + } + + const mockStartServices = () => { + mockUseStartServices.mockReturnValue({ + notifications: { toasts: {} }, + }); + }; + + beforeEach(() => { + mockStartServices(); + }); + + it('should render output health component when degraded', async () => { + mockSendGetOutputHealth.mockResolvedValue({ + data: { state: 'DEGRADED', message: 'connection error', timestamp: '2023-11-30T14:25:31Z' }, + }); + const { utils } = render({ + type: 'remote_elasticsearch', + id: 'remote', + name: 'Remote ES', + hosts: ['https://remote-es:443'], + } as Output); + + expect(mockSendGetOutputHealth).toHaveBeenCalled(); + + await waitFor(async () => { + expect(utils.getByTestId('outputHealthDegradedCallout').textContent).toContain( + 'Unable to connect to "Remote ES" at https://remote-es:443. Please check the details are correct.' + ); + }); + }); + + it('should render output health component when healthy', async () => { + mockSendGetOutputHealth.mockResolvedValue({ + data: { state: 'HEALTHY', message: '', timestamp: '2023-11-30T14:25:31Z' }, + }); + const { utils } = render({ + type: 'remote_elasticsearch', + id: 'remote', + name: 'Remote ES', + hosts: ['https://remote-es:443'], + } as Output); + + expect(mockSendGetOutputHealth).toHaveBeenCalled(); + + await waitFor(async () => { + expect(utils.getByTestId('outputHealthHealthyCallout').textContent).toContain( + 'Connection with remote output established.' + ); + }); + }); + + it('should render output health badge when degraded', async () => { + mockSendGetOutputHealth.mockResolvedValue({ + data: { state: 'DEGRADED', message: 'connection error', timestamp: '2023-11-30T14:25:31Z' }, + }); + const { utils } = render( + { + type: 'remote_elasticsearch', + id: 'remote', + name: 'Remote ES', + hosts: ['https://remote-es:443'], + } as Output, + true + ); + + expect(mockSendGetOutputHealth).toHaveBeenCalled(); + + await waitFor(async () => { + expect(utils.getByTestId('outputHealthDegradedBadge')).not.toBeNull(); + expect(utils.getByTestId('outputHealthBadgeTooltip')).not.toBeNull(); + }); + }); + + it('should render output health badge when healthy', async () => { + mockSendGetOutputHealth.mockResolvedValue({ + data: { state: 'HEALTHY', message: '', timestamp: '2023-11-30T14:25:31Z' }, + }); + const { utils } = render( + { + type: 'remote_elasticsearch', + id: 'remote', + name: 'Remote ES', + hosts: ['https://remote-es:443'], + } as Output, + true + ); + + expect(mockSendGetOutputHealth).toHaveBeenCalled(); + + await waitFor(async () => { + expect(utils.getByTestId('outputHealthHealthyBadge')).not.toBeNull(); + expect(utils.getByTestId('outputHealthBadgeTooltip')).not.toBeNull(); + }); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx index 0c401722e62d4..c26a122287d01 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_health.tsx @@ -45,7 +45,12 @@ export const OutputHealth: React.FunctionComponent = ({ output, showBadge const EditOutputStatus: { [status: string]: JSX.Element | null } = { DEGRADED: ( - +

{i18n.translate('xpack.fleet.output.calloutText', { defaultMessage: 'Unable to connect to "{name}" at {host}.', @@ -63,7 +68,12 @@ export const OutputHealth: React.FunctionComponent = ({ output, showBadge ), HEALTHY: ( - +

{i18n.translate('xpack.fleet.output.successCalloutText', { defaultMessage: 'Connection with remote output established.', @@ -75,7 +85,7 @@ export const OutputHealth: React.FunctionComponent = ({ output, showBadge const OutputStatusBadge: { [status: string]: JSX.Element | null } = { DEGRADED: ( - + = ({ output, showBadge ), HEALTHY: ( - + = ({ output, showBadge return showBadge ? ( lastTimestampText && outputHealth?.state ? ( - + <>{outputBadge} ) : ( From 264d3b6a97e8d3cdfd826f161b4782c379b55a3b Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Thu, 30 Nov 2023 16:59:51 +0100 Subject: [PATCH 09/10] convert timestamp to string --- x-pack/test/fleet_api_integration/apis/outputs/crud.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/test/fleet_api_integration/apis/outputs/crud.ts b/x-pack/test/fleet_api_integration/apis/outputs/crud.ts index c015f919ce425..67ba6b9811f38 100644 --- a/x-pack/test/fleet_api_integration/apis/outputs/crud.ts +++ b/x-pack/test/fleet_api_integration/apis/outputs/crud.ts @@ -200,7 +200,7 @@ export default function (providerContext: FtrProviderContext) { document: { state: 'HEALTHY', message: '', - '@timestamp': Date.parse('2023-11-29T14:25:31Z'), + '@timestamp': '' + Date.parse('2023-11-29T14:25:31Z'), output: defaultOutputId, }, }); @@ -211,7 +211,7 @@ export default function (providerContext: FtrProviderContext) { document: { state: 'DEGRADED', message: 'connection error', - '@timestamp': Date.parse('2023-11-30T14:25:31Z'), + '@timestamp': '' + Date.parse('2023-11-30T14:25:31Z'), output: defaultOutputId, }, }); @@ -222,7 +222,7 @@ export default function (providerContext: FtrProviderContext) { document: { state: 'HEALTHY', message: '', - '@timestamp': Date.parse('2023-11-31T14:25:31Z'), + '@timestamp': '' + Date.parse('2023-11-31T14:25:31Z'), output: 'remote2', }, }); From 1b258bddd116083242d5c50c2f8fe4573757282e Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Mon, 4 Dec 2023 09:00:38 +0100 Subject: [PATCH 10/10] added openapi spec --- .../plugins/fleet/common/openapi/bundled.json | 48 +++++++++++++++++++ .../plugins/fleet/common/openapi/bundled.yaml | 31 ++++++++++++ .../fleet/common/openapi/entrypoint.yaml | 2 + .../paths/output_health@{output_id}.yaml | 31 ++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 x-pack/plugins/fleet/common/openapi/paths/output_health@{output_id}.yaml diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 102a80ad003fd..e90a0799c3b98 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -4680,6 +4680,54 @@ ] } }, + "/outputs/{outputId}/health": { + "get": { + "summary": "Get latest output health", + "tags": [ + "Outputs" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "state": { + "type": "string", + "description": "state of output, HEALTHY or DEGRADED" + }, + "message": { + "type": "string", + "description": "long message if unhealthy" + }, + "timestamp": { + "type": "string", + "description": "timestamp of reported state" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/error" + } + }, + "operationId": "get-output-health" + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "outputId", + "in": "path", + "required": true + } + ] + }, "/logstash_api_keys": { "post": { "summary": "Generate Logstash API key", diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 2e47aaf003062..cd4916bca6452 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -2916,6 +2916,37 @@ paths: $ref: '#/components/responses/error' parameters: - $ref: '#/components/parameters/kbn_xsrf' + /outputs/{outputId}/health: + get: + summary: Get latest output health + tags: + - Outputs + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + state: + type: string + description: state of output, HEALTHY or DEGRADED + message: + type: string + description: long message if unhealthy + timestamp: + type: string + description: timestamp of reported state + '400': + $ref: '#/components/responses/error' + operationId: get-output-health + parameters: + - schema: + type: string + name: outputId + in: path + required: true /logstash_api_keys: post: summary: Generate Logstash API key diff --git a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml index 92bffe4968092..8c390ea56c261 100644 --- a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml +++ b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml @@ -142,6 +142,8 @@ paths: $ref: paths/outputs.yaml /outputs/{outputId}: $ref: paths/outputs@{output_id}.yaml + /outputs/{outputId}/health: + $ref: paths/output_health@{output_id}.yaml /logstash_api_keys: $ref: paths/logstash_api_keys.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/output_health@{output_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/output_health@{output_id}.yaml new file mode 100644 index 0000000000000..b53936b8859ea --- /dev/null +++ b/x-pack/plugins/fleet/common/openapi/paths/output_health@{output_id}.yaml @@ -0,0 +1,31 @@ +get: + summary: Get latest output health + tags: + - Outputs + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + state: + type: string + description: state of output, HEALTHY or DEGRADED + message: + type: string + description: long message if unhealthy + timestamp: + type: string + description: timestamp of reported state + '400': + $ref: ../components/responses/error.yaml + operationId: get-output-health +parameters: + - schema: + type: string + name: outputId + in: path + required: true +