mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Fleet] Spacetime: Add fleet server health check to debug page (#146594)
## Summary
Closes https://github.com/elastic/ingest-dev/issues/1422
Closes https://github.com/elastic/kibana/issues/143644
Fixes https://github.com/elastic/kibana/issues/141635
### Description
Spacetime project:
- Fixing UI layout
- Adding a new endpoint to check the connection to fleet-server. The
endpoint executes `curl -s -k <hostname>/api/status` and can be called
from dev tools as:
```
POST kbn:/api/fleet/health_check
{
"host": $hostname
}
```
Where `$hostname` is the host configured in fleet server hosts settings
section.
- Adding a Fleet Server health check to the debug page
`app/fleet/_debug`. The host can be selected via a dropdown.
- Moving debug page outside of setup to allow debugging when fleet
couldn't initialise. I added a warning on top of the page in this
specific case - https://github.com/elastic/kibana/issues/143644
- Added some more saved objects and indices that weren't added the first
time round to allow further debugging.
### Repro steps:
To try that the page loads even when setup didn't work, I changed some
values in the code. Comment out [this
code](https://github.com/elastic/kibana/blob/main/x-pack/plugins/fleet/public/applications/fleet/app.tsx#L163-L165)
and replace it with `setInitializationError({ name: 'error', message:
'unable to initialize' });`, then set `false` in
[here](b155134d66/x-pack/plugins/fleet/public/applications/fleet/app.tsx (L179)
)
and the fleet UI should show a setup error, however now the debug page
should be visible.
### Screenshots
<img width="2076" alt="Screenshot 2022-11-30 at 15 25 51"
src="https://user-images.githubusercontent.com/16084106/204824818-d620aabf-83b1-4acd-9f38-6f271d17a38a.png">
<img width="1403" alt="Screenshot 2022-11-30 at 15 26 36"
src="https://user-images.githubusercontent.com/16084106/204824851-04b36d5e-e466-4f0c-9eed-b8b492f128b9.png">
<img width="2063" alt="Screenshot 2022-11-30 at 15 27 09"
src="https://user-images.githubusercontent.com/16084106/204824909-a26a8df1-38ba-4553-984f-fce13a3abf8d.png">
<img width="2110" alt="Screenshot 2022-12-01 at 17 36 57"
src="https://user-images.githubusercontent.com/16084106/205110349-a682a894-767e-47f9-beb9-7f9c39bece72.png">
### Checklist
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
b5ba42bf1a
commit
035ebc4106
16 changed files with 424 additions and 106 deletions
|
@ -259,6 +259,7 @@ export const settingsRoutesService = {
|
|||
export const appRoutesService = {
|
||||
getCheckPermissionsPath: (fleetServerSetup?: boolean) => APP_API_ROUTES.CHECK_PERMISSIONS_PATTERN,
|
||||
getRegenerateServiceTokenPath: () => APP_API_ROUTES.GENERATE_SERVICE_TOKEN_PATTERN,
|
||||
postHealthCheckPath: () => APP_API_ROUTES.HEALTH_CHECK_PATTERN,
|
||||
};
|
||||
|
||||
export const enrollmentAPIKeyRouteService = {
|
||||
|
|
18
x-pack/plugins/fleet/common/types/rest_spec/health_check.ts
Normal file
18
x-pack/plugins/fleet/common/types/rest_spec/health_check.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface PostHealthCheckRequest {
|
||||
body: {
|
||||
host: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PostHealthCheckResponse {
|
||||
name: string;
|
||||
host: string;
|
||||
status: string;
|
||||
}
|
|
@ -18,3 +18,4 @@ export * from './fleet_setup';
|
|||
export * from './output';
|
||||
export * from './package_policy';
|
||||
export * from './settings';
|
||||
export * from './health_check';
|
||||
|
|
|
@ -146,6 +146,7 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
|
|||
const [initializationError, setInitializationError] = useState<Error | null>(null);
|
||||
|
||||
const isAddIntegrationsPath = !!useRouteMatch(FLEET_ROUTING_PATHS.add_integration_to_policy);
|
||||
const isDebugPath = !!useRouteMatch(FLEET_ROUTING_PATHS.debug);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
|
@ -193,6 +194,10 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
|
|||
</ErrorLayout>
|
||||
);
|
||||
}
|
||||
// Debug page moved outside of initialization to allow debugging when setup failed
|
||||
if (isDebugPath) {
|
||||
return <DebugPage setupError={initializationError} isInitialized={isInitialized} />;
|
||||
}
|
||||
|
||||
if (!isInitialized || initializationError) {
|
||||
return (
|
||||
|
@ -328,10 +333,6 @@ export const AppRoutes = memo(
|
|||
<SettingsApp />
|
||||
</Route>
|
||||
|
||||
<Route path={FLEET_ROUTING_PATHS.debug}>
|
||||
<DebugPage />
|
||||
</Route>
|
||||
|
||||
{/* TODO: Move this route to the Integrations app */}
|
||||
<Route path={FLEET_ROUTING_PATHS.add_integration_to_policy}>
|
||||
<CreatePackagePolicyPage />
|
||||
|
|
|
@ -43,6 +43,8 @@ export const FleetIndexDebugger = () => {
|
|||
const indices = [
|
||||
{ label: '.fleet-agents', value: '.fleet-agents' },
|
||||
{ label: '.fleet-actions', value: '.fleet-actions' },
|
||||
{ label: '.fleet-servers', value: '.fleet-servers' },
|
||||
{ label: '.fleet-enrollment-api-keys', value: '.fleet-enrollment-api-keys' },
|
||||
];
|
||||
const [index, setIndex] = useState<string | undefined>();
|
||||
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* 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, { useEffect, useState, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiSuperSelect,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiCallOut,
|
||||
EuiHealth,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { sendPostHealthCheck, useGetFleetServerHosts } from '../../../hooks';
|
||||
import type { FleetServerHost } from '../../../types';
|
||||
|
||||
const POLLING_INTERVAL_S = 10; // 10 sec
|
||||
const POLLING_INTERVAL_MS = POLLING_INTERVAL_S * 1000;
|
||||
|
||||
export const HealthCheckPanel: React.FunctionComponent = () => {
|
||||
const [selectedFleetServerHost, setSelectedFleetServerHost] = useState<
|
||||
FleetServerHost | undefined
|
||||
>();
|
||||
|
||||
const { data } = useGetFleetServerHosts();
|
||||
const fleetServerHosts = useMemo(() => data?.items ?? [], [data?.items]);
|
||||
|
||||
useEffect(() => {
|
||||
const defaultHost = fleetServerHosts.find((item) => item.is_default === true);
|
||||
if (defaultHost) {
|
||||
setSelectedFleetServerHost(defaultHost);
|
||||
}
|
||||
}, [fleetServerHosts]);
|
||||
|
||||
const hostName = useMemo(
|
||||
() => selectedFleetServerHost?.host_urls[0] || '',
|
||||
[selectedFleetServerHost?.host_urls]
|
||||
);
|
||||
|
||||
const [healthData, setHealthData] = useState<any>();
|
||||
|
||||
const { data: healthCheckResponse } = useQuery(
|
||||
['fleetServerHealth', hostName],
|
||||
() => sendPostHealthCheck({ host: hostName }),
|
||||
{ refetchInterval: POLLING_INTERVAL_MS }
|
||||
);
|
||||
useEffect(() => {
|
||||
setHealthData(healthCheckResponse);
|
||||
}, [healthCheckResponse]);
|
||||
|
||||
const fleetServerHostsOptions = useMemo(
|
||||
() => [
|
||||
...fleetServerHosts.map((fleetServerHost) => {
|
||||
return {
|
||||
inputDisplay: `${fleetServerHost.name} (${fleetServerHost.host_urls[0]})`,
|
||||
value: fleetServerHost.id,
|
||||
};
|
||||
}),
|
||||
],
|
||||
[fleetServerHosts]
|
||||
);
|
||||
|
||||
const healthStatus = (statusValue: string) => {
|
||||
if (!statusValue) return null;
|
||||
|
||||
let color;
|
||||
switch (statusValue) {
|
||||
case 'HEALTHY':
|
||||
color = 'success';
|
||||
break;
|
||||
case 'UNHEALTHY':
|
||||
color = 'warning';
|
||||
break;
|
||||
case 'OFFLINE':
|
||||
color = 'subdued';
|
||||
break;
|
||||
default:
|
||||
color = 'subdued';
|
||||
}
|
||||
|
||||
return <EuiHealth color={color}>{statusValue}</EuiHealth>;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText grow={false}>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.healthCheckPanel.description"
|
||||
defaultMessage="Select the host used to enroll Fleet Server. The connection is refreshed every {interval}s."
|
||||
values={{
|
||||
interval: POLLING_INTERVAL_S,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={`
|
||||
min-width: 600px;
|
||||
`}
|
||||
>
|
||||
<EuiSuperSelect
|
||||
fullWidth
|
||||
data-test-subj="fleetDebug.fleetServerHostsSelect"
|
||||
prepend={
|
||||
<EuiText size="relative" color={''}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.healthCheckPanel.fleetServerHostsLabel"
|
||||
defaultMessage="Fleet Server Hosts"
|
||||
/>
|
||||
</EuiText>
|
||||
}
|
||||
onChange={(fleetServerHostId) => {
|
||||
setHealthData(undefined);
|
||||
setSelectedFleetServerHost(
|
||||
fleetServerHosts.find((fleetServerHost) => fleetServerHost.id === fleetServerHostId)
|
||||
);
|
||||
}}
|
||||
valueOfSelected={selectedFleetServerHost?.id}
|
||||
options={fleetServerHostsOptions}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{healthData?.data?.status && hostName === healthData?.data?.host ? (
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.healthCheckPanel.status"
|
||||
defaultMessage="Status:"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>{healthStatus(healthData?.data?.status)}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{healthData?.error && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut title="Error" color="danger">
|
||||
{healthData?.error?.message ?? (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.healthCheckPanel.fetchError"
|
||||
defaultMessage="Message: {errorMessage}"
|
||||
values={{
|
||||
errorMessage: healthData?.error?.message,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</EuiCallOut>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -11,3 +11,4 @@ export * from './saved_object_debugger';
|
|||
export * from './preconfiguration_debugger';
|
||||
export * from './fleet_index_debugger';
|
||||
export * from './orphaned_integration_policy_debugger';
|
||||
export * from './health_check_panel';
|
||||
|
|
|
@ -129,7 +129,7 @@ export const PreconfigurationDebugger: React.FunctionComponent = () => {
|
|||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.preconfigurationDebugger.description"
|
||||
defaultMessage="This tool can be used to reset preconfigured policies that are managed via {codeKibanaYml}. This includes Fleet's default policies that may existin cloud environments."
|
||||
defaultMessage="This tool can be used to reset preconfigured policies that are managed via {codeKibanaYml}. This includes Fleet's default policies that may exist in cloud environments."
|
||||
values={{ codeKibanaYml: <EuiCode>kibana.yml</EuiCode> }}
|
||||
/>
|
||||
</p>
|
||||
|
|
|
@ -22,6 +22,15 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
|
||||
import { sendRequest } from '../../../hooks';
|
||||
|
||||
import {
|
||||
OUTPUT_SAVED_OBJECT_TYPE,
|
||||
AGENT_POLICY_SAVED_OBJECT_TYPE,
|
||||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
PACKAGES_SAVED_OBJECT_TYPE,
|
||||
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
} from '../../../../../../common/constants';
|
||||
|
||||
import { CodeBlock } from './code_block';
|
||||
import { SavedObjectNamesCombo } from './saved_object_names_combo';
|
||||
|
||||
|
@ -61,29 +70,41 @@ const fetchSavedObjects = async (type?: string, name?: string) => {
|
|||
export const SavedObjectDebugger: React.FunctionComponent = () => {
|
||||
const types = [
|
||||
{
|
||||
value: 'ingest-agent-policies',
|
||||
value: `${AGENT_POLICY_SAVED_OBJECT_TYPE}`,
|
||||
text: i18n.translate('xpack.fleet.debug.savedObjectDebugger.agentPolicyLabel', {
|
||||
defaultMessage: 'Agent policy',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'ingest-package-policies',
|
||||
value: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}`,
|
||||
text: i18n.translate('xpack.fleet.debug.savedObjectDebugger.packagePolicyLabel', {
|
||||
defaultMessage: 'Integration policy',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'ingest-outputs',
|
||||
value: `${OUTPUT_SAVED_OBJECT_TYPE}`,
|
||||
text: i18n.translate('xpack.fleet.debug.savedObjectDebugger.outputLabel', {
|
||||
defaultMessage: 'Output',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'epm-packages',
|
||||
value: `${PACKAGES_SAVED_OBJECT_TYPE}`,
|
||||
text: i18n.translate('xpack.fleet.debug.savedObjectDebugger.packageLabel', {
|
||||
defaultMessage: 'Packages',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: `${DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE}`,
|
||||
text: i18n.translate('xpack.fleet.debug.savedObjectDebugger.downloadSourceLabel', {
|
||||
defaultMessage: 'Download Sources',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: `${FLEET_SERVER_HOST_SAVED_OBJECT_TYPE}`,
|
||||
text: i18n.translate('xpack.fleet.debug.savedObjectDebugger.fleetServerHostLabel', {
|
||||
defaultMessage: 'Fleet Server Hosts',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const [type, setType] = useState(types[0].value);
|
||||
|
@ -153,7 +174,7 @@ export const SavedObjectDebugger: React.FunctionComponent = () => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{(status === 'error' || namesStatus === 'error') && (
|
||||
{savedObjectResult && (status === 'error' || namesStatus === 'error') && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut title="Error" color="danger">
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
EuiPage,
|
||||
EuiPageBody,
|
||||
EuiPageHeader,
|
||||
EuiPageSection,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
|
@ -24,6 +25,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import type { RequestError } from '../../hooks';
|
||||
import { useLink, useStartServices } from '../../hooks';
|
||||
|
||||
import {
|
||||
|
@ -33,6 +35,7 @@ import {
|
|||
FleetIndexDebugger,
|
||||
SavedObjectDebugger,
|
||||
OrphanedIntegrationPolicyDebugger,
|
||||
HealthCheckPanel,
|
||||
} from './components';
|
||||
|
||||
// TODO: Evaluate moving this react-query initialization up to the main Fleet app
|
||||
|
@ -40,6 +43,13 @@ import {
|
|||
export const queryClient = new QueryClient();
|
||||
|
||||
const panels = [
|
||||
{
|
||||
title: i18n.translate('xpack.fleet.debug.HealthCheckStatus.title', {
|
||||
defaultMessage: 'Health Check Status',
|
||||
}),
|
||||
id: 'healthCheckStatus',
|
||||
component: <HealthCheckPanel />,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.fleet.debug.agentPolicyDebugger.title', {
|
||||
defaultMessage: 'Agent Policy Debugger',
|
||||
|
@ -84,7 +94,10 @@ const panels = [
|
|||
},
|
||||
];
|
||||
|
||||
export const DebugPage: React.FunctionComponent = () => {
|
||||
export const DebugPage: React.FunctionComponent<{
|
||||
isInitialized: boolean;
|
||||
setupError: RequestError | null;
|
||||
}> = ({ isInitialized, setupError }) => {
|
||||
const { chrome } = useStartServices();
|
||||
const { getHref } = useLink();
|
||||
|
||||
|
@ -92,93 +105,113 @@ export const DebugPage: React.FunctionComponent = () => {
|
|||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<EuiPage>
|
||||
<EuiPage paddingSize="xl">
|
||||
<EuiPageBody panelled>
|
||||
<EuiPageHeader
|
||||
pageTitle={i18n.translate('xpack.fleet.debug.pageTitle', {
|
||||
defaultMessage: 'Fleet Debugging Dashboard',
|
||||
})}
|
||||
iconType="wrench"
|
||||
/>
|
||||
<EuiCallOut color="danger" iconType="alert" title="Danger zone">
|
||||
<EuiText grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.dangerZone.description"
|
||||
defaultMessage="This page provides an interface for directly managing Fleet's underlying data and diagnosing issues. Be aware that these debugging tools can be {strongDestructive} in nature and can result in {strongLossOfData}. Please proceed with caution."
|
||||
values={{
|
||||
strongDestructive: (
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.dangerZone.destructive"
|
||||
defaultMessage="destructive"
|
||||
/>
|
||||
</strong>
|
||||
),
|
||||
strongLossOfData: (
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.dangerZone.lossOfData"
|
||||
defaultMessage="loss of data"
|
||||
/>
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiCallOut>
|
||||
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
{panels.map(({ title, id, component }) => (
|
||||
<>
|
||||
<EuiAccordion
|
||||
id={id}
|
||||
initialIsOpen
|
||||
buttonContent={
|
||||
<EuiTitle size="l">
|
||||
<h2>{title}</h2>
|
||||
</EuiTitle>
|
||||
}
|
||||
>
|
||||
<EuiSpacer size="m" />
|
||||
{component}
|
||||
</EuiAccordion>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
</>
|
||||
))}
|
||||
|
||||
<EuiTitle size="l">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.usefulLinks.title"
|
||||
defaultMessage="Useful links"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiPageSection>
|
||||
<EuiPageHeader
|
||||
pageTitle={i18n.translate('xpack.fleet.debug.pageTitle', {
|
||||
defaultMessage: 'Fleet Debugging Dashboard',
|
||||
})}
|
||||
iconType="wrench"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut color="danger" iconType="alert" title="Danger zone">
|
||||
<EuiText grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.dangerZone.description"
|
||||
defaultMessage="This page provides an interface for directly managing Fleet's underlying data and diagnosing issues. Be aware that these debugging tools can be {strongDestructive} in nature and can result in {strongLossOfData}. Please proceed with caution."
|
||||
values={{
|
||||
strongDestructive: (
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.dangerZone.destructive"
|
||||
defaultMessage="destructive"
|
||||
/>
|
||||
</strong>
|
||||
),
|
||||
strongLossOfData: (
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.dangerZone.lossOfData"
|
||||
defaultMessage="loss of data"
|
||||
/>
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiCallOut>
|
||||
{!isInitialized && setupError?.message && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCallOut color="danger" iconType="alert" title="Setup error">
|
||||
<EuiText grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.initializationError.description"
|
||||
defaultMessage="{message}. You can use this page to debug the error."
|
||||
values={{
|
||||
message: setupError?.message,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiCallOut>
|
||||
</>
|
||||
)}
|
||||
</EuiPageSection>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiPageSection>
|
||||
{panels.map(({ title, id, component }) => (
|
||||
<>
|
||||
<EuiAccordion
|
||||
id={id}
|
||||
initialIsOpen
|
||||
buttonContent={
|
||||
<EuiTitle size="l">
|
||||
<h2>{title}</h2>
|
||||
</EuiTitle>
|
||||
}
|
||||
>
|
||||
<EuiSpacer size="m" />
|
||||
{component}
|
||||
</EuiAccordion>
|
||||
|
||||
<EuiListGroup
|
||||
listItems={[
|
||||
{
|
||||
label: i18n.translate('xpack.fleet.debug.usefulLinks.viewAgents', {
|
||||
defaultMessage: 'View Agents in Fleet UI',
|
||||
}),
|
||||
href: getHref('agent_list'),
|
||||
iconType: 'agentApp',
|
||||
target: '_blank',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.fleet.debug.usefulLinks.troubleshootingGuide', {
|
||||
defaultMessage: 'Troubleshooting Guide',
|
||||
}),
|
||||
href: 'https://www.elastic.co/guide/en/fleet/current/fleet-troubleshooting.html',
|
||||
iconType: 'popout',
|
||||
target: '_blank',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<EuiHorizontalRule />
|
||||
</>
|
||||
))}
|
||||
|
||||
<EuiTitle size="l">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.debug.usefulLinks.title"
|
||||
defaultMessage="Useful links"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiListGroup
|
||||
listItems={[
|
||||
{
|
||||
label: i18n.translate('xpack.fleet.debug.usefulLinks.viewAgents', {
|
||||
defaultMessage: 'View Agents in Fleet UI',
|
||||
}),
|
||||
href: getHref('agent_list'),
|
||||
iconType: 'agentApp',
|
||||
target: '_blank',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.fleet.debug.usefulLinks.troubleshootingGuide', {
|
||||
defaultMessage: 'Troubleshooting Guide',
|
||||
}),
|
||||
href: 'https://www.elastic.co/guide/en/fleet/current/fleet-troubleshooting.html',
|
||||
iconType: 'popout',
|
||||
target: '_blank',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiPageSection>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 type { PostHealthCheckRequest, PostHealthCheckResponse } from '../../types';
|
||||
import { appRoutesService } from '../../services';
|
||||
|
||||
import { sendRequest } from './use_request';
|
||||
|
||||
export function sendPostHealthCheck(body: PostHealthCheckRequest['body']) {
|
||||
return sendRequest<PostHealthCheckResponse>({
|
||||
method: 'post',
|
||||
path: appRoutesService.postHealthCheckPath(),
|
||||
body,
|
||||
});
|
||||
}
|
|
@ -20,3 +20,4 @@ export * from './ingest_pipelines';
|
|||
export * from './download_source';
|
||||
export * from './fleet_server_hosts';
|
||||
export * from './fleet_proxies';
|
||||
export * from './health_check';
|
||||
|
|
|
@ -129,6 +129,8 @@ export type {
|
|||
PostDownloadSourceRequest,
|
||||
PutDownloadSourceRequest,
|
||||
GetAvailableVersionsResponse,
|
||||
PostHealthCheckRequest,
|
||||
PostHealthCheckResponse,
|
||||
} from '../../common/types';
|
||||
export {
|
||||
entries,
|
||||
|
|
|
@ -4,31 +4,67 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import https from 'https';
|
||||
|
||||
import type { TypeOf } from '@kbn/config-schema';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
import { APP_API_ROUTES } from '../../constants';
|
||||
import type { FleetRequestHandler } from '../../types';
|
||||
import type { FleetAuthzRouter } from '../security';
|
||||
import { defaultFleetErrorHandler } from '../../errors';
|
||||
import { PostHealthCheckRequestSchema } from '../../types';
|
||||
|
||||
export const registerRoutes = (router: FleetAuthzRouter) => {
|
||||
router.get(
|
||||
// get fleet server health check by host
|
||||
router.post(
|
||||
{
|
||||
path: APP_API_ROUTES.HEALTH_CHECK_PATTERN,
|
||||
validate: {},
|
||||
validate: PostHealthCheckRequestSchema,
|
||||
fleetAuthz: {
|
||||
fleet: { all: true },
|
||||
},
|
||||
},
|
||||
getHealthCheckHandler
|
||||
postHealthCheckHandler
|
||||
);
|
||||
};
|
||||
|
||||
export const getHealthCheckHandler: FleetRequestHandler<undefined, undefined, undefined> = async (
|
||||
context,
|
||||
request,
|
||||
response
|
||||
) => {
|
||||
return response.ok({
|
||||
body: 'Fleet Health Check Report:\nFleet Server: HEALTHY',
|
||||
headers: { 'content-type': 'text/plain' },
|
||||
});
|
||||
export const postHealthCheckHandler: FleetRequestHandler<
|
||||
undefined,
|
||||
undefined,
|
||||
TypeOf<typeof PostHealthCheckRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
try {
|
||||
const abortController = new AbortController();
|
||||
const { host } = request.body;
|
||||
|
||||
// Sometimes when the host is not online, the request hangs
|
||||
// Setting a timeout to abort the request after 5s
|
||||
setTimeout(() => {
|
||||
abortController.abort();
|
||||
}, 5000);
|
||||
|
||||
const res = await fetch(`${host}/api/status`, {
|
||||
headers: {
|
||||
accept: '*/*',
|
||||
},
|
||||
method: 'GET',
|
||||
agent: new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
}),
|
||||
signal: abortController.signal,
|
||||
});
|
||||
const bodyRes = await res.json();
|
||||
const body = { ...bodyRes, host };
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
// when the request is aborted, return offline status
|
||||
if (error.name === 'AbortError') {
|
||||
return response.ok({
|
||||
body: { name: 'fleet-server', status: `OFFLINE`, host: request.body.host },
|
||||
});
|
||||
}
|
||||
return defaultFleetErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
|
14
x-pack/plugins/fleet/server/types/rest_spec/health_check.ts
Normal file
14
x-pack/plugins/fleet/server/types/rest_spec/health_check.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
|
||||
export const PostHealthCheckRequestSchema = {
|
||||
body: schema.object({
|
||||
host: schema.uri({ scheme: ['http', 'https'] }),
|
||||
}),
|
||||
};
|
|
@ -20,3 +20,4 @@ export * from './setup';
|
|||
export * from './check_permissions';
|
||||
export * from './download_sources';
|
||||
export * from './tags';
|
||||
export * from './health_check';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue