[Ingest Manager] Add UI to enroll standalone agent (#71288)
This commit is contained in:
parent
24edc804c9
commit
17dc0439e2
|
@ -11,8 +11,8 @@ const CONFIG_KEYS_ORDER = [
|
|||
'name',
|
||||
'revision',
|
||||
'type',
|
||||
'settings',
|
||||
'outputs',
|
||||
'settings',
|
||||
'inputs',
|
||||
'enabled',
|
||||
'use_output',
|
||||
|
|
|
@ -48,6 +48,17 @@ export const useGetOneAgentConfigFull = (agentConfigId: string) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const sendGetOneAgentConfigFull = (
|
||||
agentConfigId: string,
|
||||
query: { standalone?: boolean } = {}
|
||||
) => {
|
||||
return sendRequest<GetFullAgentConfigResponse>({
|
||||
path: agentConfigRouteService.getInfoFullPath(agentConfigId),
|
||||
method: 'get',
|
||||
query,
|
||||
});
|
||||
};
|
||||
|
||||
export const sendGetOneAgentConfig = (agentConfigId: string) => {
|
||||
return sendRequest<GetOneAgentConfigResponse>({
|
||||
path: agentConfigRouteService.getInfoPath(agentConfigId),
|
||||
|
|
|
@ -44,6 +44,18 @@ export function sendDeleteOneEnrollmentAPIKey(keyId: string, options?: RequestOp
|
|||
});
|
||||
}
|
||||
|
||||
export function sendGetEnrollmentAPIKeys(
|
||||
query: GetEnrollmentAPIKeysRequest['query'],
|
||||
options?: RequestOptions
|
||||
) {
|
||||
return sendRequest<GetEnrollmentAPIKeysResponse>({
|
||||
method: 'get',
|
||||
path: enrollmentAPIKeyRouteService.getListPath(),
|
||||
query,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetEnrollmentAPIKeys(
|
||||
query: GetEnrollmentAPIKeysRequest['query'],
|
||||
options?: RequestOptions
|
||||
|
|
|
@ -4,46 +4,91 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiSelect, EuiSpacer, EuiText, EuiButtonEmpty } from '@elastic/eui';
|
||||
import { AgentConfig } from '../../../../types';
|
||||
import { useGetEnrollmentAPIKeys } from '../../../../hooks';
|
||||
import { AgentConfig, GetEnrollmentAPIKeysResponse } from '../../../../types';
|
||||
import { sendGetEnrollmentAPIKeys, useCore } from '../../../../hooks';
|
||||
import { AgentConfigPackageBadges } from '../agent_config_package_badges';
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
agentConfigs: AgentConfig[];
|
||||
onKeyChange: (key: string) => void;
|
||||
}
|
||||
onConfigChange?: (key: string) => void;
|
||||
} & (
|
||||
| {
|
||||
withKeySelection: true;
|
||||
onKeyChange?: (key: string) => void;
|
||||
}
|
||||
| {
|
||||
withKeySelection: false;
|
||||
}
|
||||
);
|
||||
|
||||
export const EnrollmentStepAgentConfig: React.FC<Props> = (props) => {
|
||||
const { notifications } = useCore();
|
||||
const { withKeySelection, agentConfigs, onConfigChange } = props;
|
||||
const onKeyChange = props.withKeySelection && props.onKeyChange;
|
||||
|
||||
export const EnrollmentStepAgentConfig: React.FC<Props> = ({ agentConfigs, onKeyChange }) => {
|
||||
const [isAuthenticationSettingsOpen, setIsAuthenticationSettingsOpen] = useState(false);
|
||||
const enrollmentAPIKeysRequest = useGetEnrollmentAPIKeys({
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
});
|
||||
|
||||
const [enrollmentAPIKeys, setEnrollmentAPIKeys] = useState<GetEnrollmentAPIKeysResponse['list']>(
|
||||
[]
|
||||
);
|
||||
const [selectedState, setSelectedState] = useState<{
|
||||
agentConfigId?: string;
|
||||
enrollmentAPIKeyId?: string;
|
||||
}>({
|
||||
agentConfigId: agentConfigs.length ? agentConfigs[0].id : undefined,
|
||||
});
|
||||
const filteredEnrollmentAPIKeys = React.useMemo(() => {
|
||||
if (!selectedState.agentConfigId || !enrollmentAPIKeysRequest.data) {
|
||||
return [];
|
||||
|
||||
useEffect(() => {
|
||||
if (onConfigChange && selectedState.agentConfigId) {
|
||||
onConfigChange(selectedState.agentConfigId);
|
||||
}
|
||||
}, [selectedState.agentConfigId, onConfigChange]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!withKeySelection) {
|
||||
return;
|
||||
}
|
||||
if (!selectedState.agentConfigId) {
|
||||
setEnrollmentAPIKeys([]);
|
||||
return;
|
||||
}
|
||||
|
||||
return enrollmentAPIKeysRequest.data.list.filter(
|
||||
(key) => key.config_id === selectedState.agentConfigId
|
||||
);
|
||||
}, [enrollmentAPIKeysRequest.data, selectedState.agentConfigId]);
|
||||
async function fetchEnrollmentAPIKeys() {
|
||||
try {
|
||||
const res = await sendGetEnrollmentAPIKeys({
|
||||
page: 1,
|
||||
perPage: 10000,
|
||||
});
|
||||
if (res.error) {
|
||||
throw res.error;
|
||||
}
|
||||
|
||||
if (!res.data) {
|
||||
throw new Error('No data while fetching enrollment API keys');
|
||||
}
|
||||
|
||||
setEnrollmentAPIKeys(
|
||||
res.data.list.filter((key) => key.config_id === selectedState.agentConfigId)
|
||||
);
|
||||
} catch (error) {
|
||||
notifications.toasts.addError(error, {
|
||||
title: 'Error',
|
||||
});
|
||||
}
|
||||
}
|
||||
fetchEnrollmentAPIKeys();
|
||||
}, [withKeySelection, selectedState.agentConfigId, notifications.toasts]);
|
||||
|
||||
// Select first API key when config change
|
||||
React.useEffect(() => {
|
||||
if (!selectedState.enrollmentAPIKeyId && filteredEnrollmentAPIKeys.length > 0) {
|
||||
const enrollmentAPIKeyId = filteredEnrollmentAPIKeys[0].id;
|
||||
if (!withKeySelection || !onKeyChange) {
|
||||
return;
|
||||
}
|
||||
if (!selectedState.enrollmentAPIKeyId && enrollmentAPIKeys.length > 0) {
|
||||
const enrollmentAPIKeyId = enrollmentAPIKeys[0].id;
|
||||
setSelectedState({
|
||||
agentConfigId: selectedState.agentConfigId,
|
||||
enrollmentAPIKeyId,
|
||||
|
@ -51,7 +96,7 @@ export const EnrollmentStepAgentConfig: React.FC<Props> = ({ agentConfigs, onKey
|
|||
onKeyChange(enrollmentAPIKeyId);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [filteredEnrollmentAPIKeys, selectedState.enrollmentAPIKeyId, selectedState.agentConfigId]);
|
||||
}, [enrollmentAPIKeys, selectedState.enrollmentAPIKeyId, selectedState.agentConfigId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -85,43 +130,47 @@ export const EnrollmentStepAgentConfig: React.FC<Props> = ({ agentConfigs, onKey
|
|||
{selectedState.agentConfigId && (
|
||||
<AgentConfigPackageBadges agentConfigId={selectedState.agentConfigId} />
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButtonEmpty
|
||||
flush="left"
|
||||
iconType={isAuthenticationSettingsOpen ? 'arrowDown' : 'arrowRight'}
|
||||
onClick={() => setIsAuthenticationSettingsOpen(!isAuthenticationSettingsOpen)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.enrollmentStepAgentConfig.showAuthenticationSettingsButton"
|
||||
defaultMessage="Authentication settings"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
{isAuthenticationSettingsOpen && (
|
||||
{withKeySelection && onKeyChange && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSelect
|
||||
fullWidth
|
||||
options={filteredEnrollmentAPIKeys.map((key) => ({
|
||||
value: key.id,
|
||||
text: key.name,
|
||||
}))}
|
||||
value={selectedState.enrollmentAPIKeyId || undefined}
|
||||
prepend={
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.enrollmentStepAgentConfig.enrollmentTokenSelectLabel"
|
||||
defaultMessage="Enrollment token"
|
||||
/>
|
||||
</EuiText>
|
||||
}
|
||||
onChange={(e) => {
|
||||
setSelectedState({
|
||||
...selectedState,
|
||||
enrollmentAPIKeyId: e.target.value,
|
||||
});
|
||||
onKeyChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<EuiButtonEmpty
|
||||
flush="left"
|
||||
iconType={isAuthenticationSettingsOpen ? 'arrowDown' : 'arrowRight'}
|
||||
onClick={() => setIsAuthenticationSettingsOpen(!isAuthenticationSettingsOpen)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.enrollmentStepAgentConfig.showAuthenticationSettingsButton"
|
||||
defaultMessage="Authentication settings"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
{isAuthenticationSettingsOpen && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSelect
|
||||
fullWidth
|
||||
options={enrollmentAPIKeys.map((key) => ({
|
||||
value: key.id,
|
||||
text: key.name,
|
||||
}))}
|
||||
value={selectedState.enrollmentAPIKeyId || undefined}
|
||||
prepend={
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.enrollmentStepAgentConfig.enrollmentTokenSelectLabel"
|
||||
defaultMessage="Enrollment token"
|
||||
/>
|
||||
</EuiText>
|
||||
}
|
||||
onChange={(e) => {
|
||||
setSelectedState({
|
||||
...selectedState,
|
||||
enrollmentAPIKeyId: e.target.value,
|
||||
});
|
||||
onKeyChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -14,23 +14,13 @@ import {
|
|||
EuiButtonEmpty,
|
||||
EuiButton,
|
||||
EuiFlyoutFooter,
|
||||
EuiSteps,
|
||||
EuiText,
|
||||
EuiLink,
|
||||
EuiTab,
|
||||
EuiTabs,
|
||||
} from '@elastic/eui';
|
||||
import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { AgentConfig } from '../../../../types';
|
||||
import { EnrollmentStepAgentConfig } from './config_selection';
|
||||
import {
|
||||
useGetOneEnrollmentAPIKey,
|
||||
useCore,
|
||||
useGetSettings,
|
||||
useLink,
|
||||
useFleetStatus,
|
||||
} from '../../../../hooks';
|
||||
import { ManualInstructions } from '../../../../components/enrollment_instructions';
|
||||
import { ManagedInstructions } from './managed_instructions';
|
||||
import { StandaloneInstructions } from './standalone_instructions';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
|
@ -41,99 +31,40 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({
|
|||
onClose,
|
||||
agentConfigs = [],
|
||||
}) => {
|
||||
const { getHref } = useLink();
|
||||
const core = useCore();
|
||||
const fleetStatus = useFleetStatus();
|
||||
|
||||
const [selectedAPIKeyId, setSelectedAPIKeyId] = useState<string | undefined>();
|
||||
|
||||
const settings = useGetSettings();
|
||||
const apiKey = useGetOneEnrollmentAPIKey(selectedAPIKeyId);
|
||||
|
||||
const kibanaUrl =
|
||||
settings.data?.item?.kibana_url ?? `${window.location.origin}${core.http.basePath.get()}`;
|
||||
const kibanaCASha256 = settings.data?.item?.kibana_ca_sha256;
|
||||
|
||||
const steps: EuiContainedStepProps[] = [
|
||||
{
|
||||
title: i18n.translate('xpack.ingestManager.agentEnrollment.stepDownloadAgentTitle', {
|
||||
defaultMessage: 'Download the Elastic Agent',
|
||||
}),
|
||||
children: (
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.downloadDescription"
|
||||
defaultMessage="Download the Elastic agent on your host’s machine. You can download the agent binary and it’s verification signature from Elastic’s {downloadLink}."
|
||||
values={{
|
||||
downloadLink: (
|
||||
<EuiLink href="https://ela.st/download-elastic-agent" target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.downloadLink"
|
||||
defaultMessage="download page"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.ingestManager.agentEnrollment.stepChooseAgentConfigTitle', {
|
||||
defaultMessage: 'Choose an agent configuration',
|
||||
}),
|
||||
children: (
|
||||
<EnrollmentStepAgentConfig agentConfigs={agentConfigs} onKeyChange={setSelectedAPIKeyId} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.ingestManager.agentEnrollment.stepRunAgentTitle', {
|
||||
defaultMessage: 'Enroll and run the Elastic Agent',
|
||||
}),
|
||||
children: apiKey.data && (
|
||||
<ManualInstructions
|
||||
apiKey={apiKey.data.item}
|
||||
kibanaUrl={kibanaUrl}
|
||||
kibanaCASha256={kibanaCASha256}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
const [mode, setMode] = useState<'managed' | 'standalone'>('managed');
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={onClose} size="l" maxWidth={860}>
|
||||
<EuiFlyout onClose={onClose} size="l" maxWidth={880}>
|
||||
<EuiFlyoutHeader hasBorder aria-labelledby="FleetAgentEnrollmentFlyoutTitle">
|
||||
<EuiTitle size="m">
|
||||
<h2 id="FleetAgentEnrollmentFlyoutTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.flyoutTitle"
|
||||
defaultMessage="Enroll new agent"
|
||||
defaultMessage="Add agent"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
{fleetStatus.isReady ? (
|
||||
<>
|
||||
<EuiSteps steps={steps} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<EuiTabs style={{ marginBottom: '-25px' }}>
|
||||
<EuiTab isSelected={mode === 'managed'} onClick={() => setMode('managed')}>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.fleetNotInitializedText"
|
||||
defaultMessage="Fleet needs to be set up before agents can be enrolled. {link}"
|
||||
values={{
|
||||
link: (
|
||||
<EuiLink href={getHref('fleet')}>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.goToFleetButton"
|
||||
defaultMessage="Go to Fleet."
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
id="xpack.ingestManager.agentEnrollment.enrollFleetTabLabel"
|
||||
defaultMessage="Enroll with Fleet"
|
||||
/>
|
||||
</>
|
||||
</EuiTab>
|
||||
<EuiTab isSelected={mode === 'standalone'} onClick={() => setMode('standalone')}>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.enrollStandaloneTabLabel"
|
||||
defaultMessage="Standalone mode"
|
||||
/>
|
||||
</EuiTab>
|
||||
</EuiTabs>
|
||||
</EuiFlyoutHeader>
|
||||
|
||||
<EuiFlyoutBody>
|
||||
{mode === 'managed' ? (
|
||||
<ManagedInstructions agentConfigs={agentConfigs} />
|
||||
) : (
|
||||
<StandaloneInstructions agentConfigs={agentConfigs} />
|
||||
)}
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { EuiSteps, EuiLink, EuiText, EuiSpacer } from '@elastic/eui';
|
||||
import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { AgentConfig } from '../../../../types';
|
||||
import {
|
||||
useGetOneEnrollmentAPIKey,
|
||||
useCore,
|
||||
useGetSettings,
|
||||
useLink,
|
||||
useFleetStatus,
|
||||
} from '../../../../hooks';
|
||||
import { ManualInstructions } from '../../../../components/enrollment_instructions';
|
||||
import { DownloadStep, AgentConfigSelectionStep } from './steps';
|
||||
|
||||
interface Props {
|
||||
agentConfigs: AgentConfig[];
|
||||
}
|
||||
|
||||
export const ManagedInstructions: React.FunctionComponent<Props> = ({ agentConfigs = [] }) => {
|
||||
const { getHref } = useLink();
|
||||
const core = useCore();
|
||||
const fleetStatus = useFleetStatus();
|
||||
|
||||
const [selectedAPIKeyId, setSelectedAPIKeyId] = useState<string | undefined>();
|
||||
|
||||
const settings = useGetSettings();
|
||||
const apiKey = useGetOneEnrollmentAPIKey(selectedAPIKeyId);
|
||||
|
||||
const kibanaUrl =
|
||||
settings.data?.item?.kibana_url ?? `${window.location.origin}${core.http.basePath.get()}`;
|
||||
const kibanaCASha256 = settings.data?.item?.kibana_ca_sha256;
|
||||
|
||||
const steps: EuiContainedStepProps[] = [
|
||||
DownloadStep(),
|
||||
AgentConfigSelectionStep({ agentConfigs, setSelectedAPIKeyId }),
|
||||
{
|
||||
title: i18n.translate('xpack.ingestManager.agentEnrollment.stepEnrollAndRunAgentTitle', {
|
||||
defaultMessage: 'Enroll and start the Elastic Agent',
|
||||
}),
|
||||
children: apiKey.data && (
|
||||
<ManualInstructions
|
||||
apiKey={apiKey.data.item}
|
||||
kibanaUrl={kibanaUrl}
|
||||
kibanaCASha256={kibanaCASha256}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.managedDescription"
|
||||
defaultMessage="Whether you need one agent or thousands, Fleet makes it easy to centrally manage and deploy updates to your agents. Follow the instructions below to download and enroll an Elastic Agent with Fleet."
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
{fleetStatus.isReady ? (
|
||||
<>
|
||||
<EuiSteps steps={steps} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.fleetNotInitializedText"
|
||||
defaultMessage="Fleet needs to be set up before agents can be enrolled. {link}"
|
||||
values={{
|
||||
link: (
|
||||
<EuiLink href={getHref('fleet')}>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.goToFleetButton"
|
||||
defaultMessage="Go to Fleet."
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}{' '}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
EuiSteps,
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiButton,
|
||||
EuiCode,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiCodeBlock,
|
||||
EuiCopy,
|
||||
} from '@elastic/eui';
|
||||
import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { AgentConfig } from '../../../../types';
|
||||
import { useCore, sendGetOneAgentConfigFull } from '../../../../hooks';
|
||||
import { DownloadStep, AgentConfigSelectionStep } from './steps';
|
||||
import { configToYaml, agentConfigRouteService } from '../../../../services';
|
||||
|
||||
interface Props {
|
||||
agentConfigs: AgentConfig[];
|
||||
}
|
||||
|
||||
const RUN_INSTRUCTIONS = './elastic-agent run';
|
||||
|
||||
export const StandaloneInstructions: React.FunctionComponent<Props> = ({ agentConfigs = [] }) => {
|
||||
const core = useCore();
|
||||
const { notifications } = core;
|
||||
|
||||
const [selectedConfigId, setSelectedConfigId] = useState<string | undefined>();
|
||||
const [fullAgentConfig, setFullAgentConfig] = useState<any | undefined>();
|
||||
|
||||
const downloadLink = selectedConfigId
|
||||
? core.http.basePath.prepend(
|
||||
`${agentConfigRouteService.getInfoFullDownloadPath(selectedConfigId)}?standalone=true`
|
||||
)
|
||||
: undefined;
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchFullConfig() {
|
||||
try {
|
||||
if (!selectedConfigId) {
|
||||
return;
|
||||
}
|
||||
const res = await sendGetOneAgentConfigFull(selectedConfigId, { standalone: true });
|
||||
if (res.error) {
|
||||
throw res.error;
|
||||
}
|
||||
|
||||
if (!res.data) {
|
||||
throw new Error('No data while fetching full agent config');
|
||||
}
|
||||
|
||||
setFullAgentConfig(res.data.item);
|
||||
} catch (error) {
|
||||
notifications.toasts.addError(error, {
|
||||
title: 'Error',
|
||||
});
|
||||
}
|
||||
}
|
||||
fetchFullConfig();
|
||||
}, [selectedConfigId, notifications.toasts]);
|
||||
|
||||
const yaml = useMemo(() => configToYaml(fullAgentConfig), [fullAgentConfig]);
|
||||
const steps: EuiContainedStepProps[] = [
|
||||
DownloadStep(),
|
||||
AgentConfigSelectionStep({ agentConfigs, setSelectedConfigId }),
|
||||
{
|
||||
title: i18n.translate('xpack.ingestManager.agentEnrollment.stepConfigureAgentTitle', {
|
||||
defaultMessage: 'Configure the agent',
|
||||
}),
|
||||
children: (
|
||||
<>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.stepConfigureAgentDescription"
|
||||
defaultMessage="Copy this configuration and put it into a file named {fileName} on the system where the Elastic Agent is installed. Don’t forget to modify {ESUsernameVariable} and {ESPasswordVariable} in the {outputSection} section of the configuration file so that it uses your actual Elasticsearch credentials."
|
||||
values={{
|
||||
fileName: <EuiCode>elastic-agent.yml</EuiCode>,
|
||||
ESUsernameVariable: <EuiCode>ES_USERNAME</EuiCode>,
|
||||
ESPasswordVariable: <EuiCode>ES_PASSWORD</EuiCode>,
|
||||
outputSection: <EuiCode>outputs</EuiCode>,
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCopy textToCopy={yaml}>
|
||||
{(copy) => (
|
||||
<EuiButton onClick={copy} iconType="copyClipboard">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.copyConfigurationButton"
|
||||
defaultMessage="Copy to clipboard"
|
||||
/>
|
||||
</EuiButton>
|
||||
)}
|
||||
</EuiCopy>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton iconType="download" href={downloadLink} isDisabled={!downloadLink}>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.downloadConfigurationButton"
|
||||
defaultMessage="Download configuration"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCodeBlock language="yaml" style={{ maxHeight: 300 }} fontSize="m">
|
||||
{yaml}
|
||||
</EuiCodeBlock>
|
||||
</EuiText>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.ingestManager.agentEnrollment.stepRunAgentTitle', {
|
||||
defaultMessage: 'Start the agent',
|
||||
}),
|
||||
children: (
|
||||
<>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.stepRunAgentDescription"
|
||||
defaultMessage="From the agent’s directory, run the following command to start the agent."
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCodeBlock fontSize="m">{RUN_INSTRUCTIONS}</EuiCodeBlock>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCopy textToCopy={RUN_INSTRUCTIONS}>
|
||||
{(copy) => (
|
||||
<EuiButton onClick={copy} iconType="copyClipboard">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.copyRunInstructionsButton"
|
||||
defaultMessage="Copy to clipboard"
|
||||
/>
|
||||
</EuiButton>
|
||||
)}
|
||||
</EuiCopy>
|
||||
</EuiText>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.ingestManager.agentEnrollment.stepCheckForDataTitle', {
|
||||
defaultMessage: 'Check for data',
|
||||
}),
|
||||
status: 'incomplete',
|
||||
children: (
|
||||
<>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.stepCheckForDataDescription"
|
||||
defaultMessage="After starting the agent, the agent should begin sending data. You can view this data on Ingest Manager’s datasets page."
|
||||
/>
|
||||
</EuiText>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.standaloneDescription"
|
||||
defaultMessage="Agents running in standalone mode need to be updated manually if you ever wish to make changes to their configuration. Follow the instructions below to download and setup an Elastic Agent in standalone mode."
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiSteps steps={steps} />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiText, EuiButton, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EnrollmentStepAgentConfig } from './config_selection';
|
||||
import { AgentConfig } from '../../../../types';
|
||||
|
||||
export const DownloadStep = () => {
|
||||
return {
|
||||
title: i18n.translate('xpack.ingestManager.agentEnrollment.stepDownloadAgentTitle', {
|
||||
defaultMessage: 'Download the Elastic Agent',
|
||||
}),
|
||||
children: (
|
||||
<>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.downloadDescription"
|
||||
defaultMessage="Download the Elastic Agent on your host’s machine. You can access the agent binaries and their verification signatures from the Elastic Agent download page."
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiButton
|
||||
href="https://ela.st/download-elastic-agent"
|
||||
target="_blank"
|
||||
iconSide="right"
|
||||
iconType="popout"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentEnrollment.downloadLink"
|
||||
defaultMessage="Go to elastic.co/downloads"
|
||||
/>
|
||||
</EuiButton>
|
||||
</>
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const AgentConfigSelectionStep = ({
|
||||
agentConfigs,
|
||||
setSelectedAPIKeyId,
|
||||
setSelectedConfigId,
|
||||
}: {
|
||||
agentConfigs: AgentConfig[];
|
||||
setSelectedAPIKeyId?: (key: string) => void;
|
||||
setSelectedConfigId?: (configId: string) => void;
|
||||
}) => {
|
||||
return {
|
||||
title: i18n.translate('xpack.ingestManager.agentEnrollment.stepChooseAgentConfigTitle', {
|
||||
defaultMessage: 'Choose an agent configuration',
|
||||
}),
|
||||
children: (
|
||||
<EnrollmentStepAgentConfig
|
||||
agentConfigs={agentConfigs}
|
||||
withKeySelection={setSelectedAPIKeyId ? true : false}
|
||||
onKeyChange={setSelectedAPIKeyId}
|
||||
onConfigChange={setSelectedConfigId}
|
||||
/>
|
||||
),
|
||||
};
|
||||
};
|
|
@ -232,15 +232,17 @@ export const deleteAgentConfigsHandler: RequestHandler<
|
|||
}
|
||||
};
|
||||
|
||||
export const getFullAgentConfig: RequestHandler<TypeOf<
|
||||
typeof GetFullAgentConfigRequestSchema.params
|
||||
>> = async (context, request, response) => {
|
||||
export const getFullAgentConfig: RequestHandler<
|
||||
TypeOf<typeof GetFullAgentConfigRequestSchema.params>,
|
||||
TypeOf<typeof GetFullAgentConfigRequestSchema.query>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
|
||||
try {
|
||||
const fullAgentConfig = await agentConfigService.getFullConfig(
|
||||
soClient,
|
||||
request.params.agentConfigId
|
||||
request.params.agentConfigId,
|
||||
{ standalone: request.query.standalone === true }
|
||||
);
|
||||
if (fullAgentConfig) {
|
||||
const body: GetFullAgentConfigResponse = {
|
||||
|
@ -264,16 +266,19 @@ export const getFullAgentConfig: RequestHandler<TypeOf<
|
|||
}
|
||||
};
|
||||
|
||||
export const downloadFullAgentConfig: RequestHandler<TypeOf<
|
||||
typeof GetFullAgentConfigRequestSchema.params
|
||||
>> = async (context, request, response) => {
|
||||
export const downloadFullAgentConfig: RequestHandler<
|
||||
TypeOf<typeof GetFullAgentConfigRequestSchema.params>,
|
||||
TypeOf<typeof GetFullAgentConfigRequestSchema.query>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const {
|
||||
params: { agentConfigId },
|
||||
} = request;
|
||||
|
||||
try {
|
||||
const fullAgentConfig = await agentConfigService.getFullConfig(soClient, agentConfigId);
|
||||
const fullAgentConfig = await agentConfigService.getFullConfig(soClient, agentConfigId, {
|
||||
standalone: request.query.standalone === true,
|
||||
});
|
||||
if (fullAgentConfig) {
|
||||
const body = configToYaml(fullAgentConfig);
|
||||
const headers: ResponseHeaders = {
|
||||
|
|
|
@ -365,7 +365,8 @@ class AgentConfigService {
|
|||
|
||||
public async getFullConfig(
|
||||
soClient: SavedObjectsClientContract,
|
||||
id: string
|
||||
id: string,
|
||||
options?: { standalone: boolean }
|
||||
): Promise<FullAgentConfig | null> {
|
||||
let config;
|
||||
|
||||
|
@ -400,6 +401,13 @@ class AgentConfigService {
|
|||
api_key,
|
||||
...outputConfig,
|
||||
};
|
||||
|
||||
if (options?.standalone) {
|
||||
delete outputs[name].api_key;
|
||||
outputs[name].username = 'ES_USERNAME';
|
||||
outputs[name].password = 'ES_PASSWORD';
|
||||
}
|
||||
|
||||
return outputs;
|
||||
},
|
||||
{} as FullAgentConfig['outputs']
|
||||
|
|
|
@ -51,5 +51,6 @@ export const GetFullAgentConfigRequestSchema = {
|
|||
}),
|
||||
query: schema.object({
|
||||
download: schema.maybe(schema.boolean()),
|
||||
standalone: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -8042,7 +8042,6 @@
|
|||
"xpack.ingestManager.agentDetails.viewAgentListTitle": "すべてのエージェント構成を表示",
|
||||
"xpack.ingestManager.agentEnrollment.cancelButtonLabel": "キャンセル",
|
||||
"xpack.ingestManager.agentEnrollment.continueButtonLabel": "続行",
|
||||
"xpack.ingestManager.agentEnrollment.downloadDescription": "ホストのコンピューターでElasticエージェントをダウンロードします。エージェントバイナリをダウンロードできます。検証署名はElasticの{downloadLink}にあります。",
|
||||
"xpack.ingestManager.agentEnrollment.downloadLink": "ダウンロードページ",
|
||||
"xpack.ingestManager.agentEnrollment.fleetNotInitializedText": "エージェントを登録する前に、フリートを設定する必要があります。{link}",
|
||||
"xpack.ingestManager.agentEnrollment.flyoutTitle": "新しいエージェントを登録",
|
||||
|
|
|
@ -8047,7 +8047,6 @@
|
|||
"xpack.ingestManager.agentDetails.viewAgentListTitle": "查看所有代理配置",
|
||||
"xpack.ingestManager.agentEnrollment.cancelButtonLabel": "取消",
|
||||
"xpack.ingestManager.agentEnrollment.continueButtonLabel": "继续",
|
||||
"xpack.ingestManager.agentEnrollment.downloadDescription": "在主机计算机上下载 Elastic 代理。可以从 Elastic 的{downloadLink}下载代理二进制文件及其验证签名。",
|
||||
"xpack.ingestManager.agentEnrollment.downloadLink": "下载页面",
|
||||
"xpack.ingestManager.agentEnrollment.fleetNotInitializedText": "注册代理前需要设置 Fleet。{link}",
|
||||
"xpack.ingestManager.agentEnrollment.flyoutTitle": "注册新代理",
|
||||
|
|
Loading…
Reference in a new issue