Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export const SETTINGS_API_ROUTES = {
// App API routes
export const APP_API_ROUTES = {
CHECK_PERMISSIONS_PATTERN: `${API_ROOT}/check-permissions`,
GENERATE_SERVICE_TOKEN_PATTERN: `${API_ROOT}/service-tokens`,
};

// Agent API routes
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/services/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export const settingsRoutesService = {

export const appRoutesService = {
getCheckPermissionsPath: () => APP_API_ROUTES.CHECK_PERMISSIONS_PATTERN,
getRegenerateServiceTokenPath: () => APP_API_ROUTES.GENERATE_SERVICE_TOKEN_PATTERN,
};

export const enrollmentAPIKeyRouteService = {
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/fleet/common/types/rest_spec/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ export interface CheckPermissionsResponse {
error?: 'MISSING_SECURITY' | 'MISSING_SUPERUSER_ROLE';
success: boolean;
}

export interface GenerateServiceTokenResponse {
name: string;
value: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { appRoutesService } from '../../services';
import type { CheckPermissionsResponse } from '../../types';
import type { CheckPermissionsResponse, GenerateServiceTokenResponse } from '../../types';

import { sendRequest } from './use_request';

Expand All @@ -16,3 +16,10 @@ export const sendGetPermissionsCheck = () => {
method: 'get',
});
};

export const sendGenerateServiceToken = () => {
return sendRequest<GenerateServiceTokenResponse>({
path: appRoutesService.getRegenerateServiceTokenPath(),
method: 'post',
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React from 'react';
import React, { useState, useMemo, useCallback } from 'react';
import {
EuiButton,
EuiFlexGroup,
Expand All @@ -16,62 +16,229 @@ import {
EuiText,
EuiLink,
EuiEmptyPrompt,
EuiSteps,
EuiCodeBlock,
EuiCallOut,
EuiSelect,
} from '@elastic/eui';
import styled from 'styled-components';
import { FormattedMessage } from 'react-intl';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';

import { useStartServices } from '../../../hooks';
import { DownloadStep } from '../components/agent_enrollment_flyout/steps';
import { useStartServices, useGetOutputs, sendGenerateServiceToken } from '../../../hooks';

const FlexItemWithMinWidth = styled(EuiFlexItem)`
min-width: 0px;
max-width: 100%;
`;

export const ContentWrapper = styled(EuiFlexGroup)`
height: 100%;
margin: 0 auto;
max-width: 800px;
`;

function renderOnPremInstructions() {
// Otherwise the copy button is over the text
const CommandCode = styled.pre({
overflow: 'scroll',
});

type PLATFORM_TYPE = 'linux-mac' | 'windows' | 'rpm-deb';
const PLATFORM_OPTIONS: Array<{ text: string; value: PLATFORM_TYPE }> = [
{ text: 'Linux / macOS', value: 'linux-mac' },
{ text: 'Windows', value: 'windows' },
{ text: 'RPM / DEB', value: 'rpm-deb' },
];

const OnPremInstructions: React.FC = () => {
const outputsRequest = useGetOutputs();
const { notifications } = useStartServices();
const [serviceToken, setServiceToken] = useState<string>();
const [isLoadingServiceToken, setIsLoadingServiceToken] = useState<boolean>(false);
const [platform, setPlatform] = useState<PLATFORM_TYPE>('linux-mac');

const output = outputsRequest.data?.items?.[0];
const esHost = output?.hosts?.[0];

const installCommand = useMemo((): string => {
if (!serviceToken || !esHost) {
return '';
}
switch (platform) {
case 'linux-mac':
return `sudo ./elastic-agent install -f --fleet-server-es=${esHost} --fleet-server-service-token=${serviceToken}`;
case 'windows':
return `.\\elastic-agent.exe install --fleet-server-es=${esHost} --fleet-server-service-token=${serviceToken}`;
case 'rpm-deb':
return `sudo elastic-agent enroll -f --fleet-server-es=${esHost} --fleet-server-service-token=${serviceToken}`;
default:
return '';
}
}, [serviceToken, esHost, platform]);

const getServiceToken = useCallback(async () => {
setIsLoadingServiceToken(true);
try {
const { data } = await sendGenerateServiceToken();
if (data?.value) {
setServiceToken(data?.value);
}
} catch (err) {
notifications.toasts.addError(err, {
title: i18n.translate('xpack.fleet.fleetServerSetup.errorGeneratingTokenTitleText', {
defaultMessage: 'Error generating token',
}),
});
}

setIsLoadingServiceToken(false);
}, [notifications]);

return (
<EuiPanel
paddingSize="none"
grow={false}
hasShadow={false}
hasBorder={true}
className="eui-textCenter"
>
<EuiEmptyPrompt
title={
<h2>
<FormattedMessage
id="xpack.fleet.fleetServerSetup.setupTitle"
defaultMessage="Add a Fleet Server"
/>
</h2>
}
body={
<EuiPanel paddingSize="l" grow={false} hasShadow={false} hasBorder={true}>
<EuiSpacer size="s" />
<EuiText className="eui-textCenter">
<h2>
<FormattedMessage
id="xpack.fleet.fleetServerSetup.setupText"
defaultMessage="A Fleet Server is required before you can enroll agents with Fleet. See the Fleet User Guide for instructions on how to add a Fleet Server."
id="xpack.fleet.fleetServerSetup.setupTitle"
defaultMessage="Add a Fleet Server"
/>
}
actions={
<EuiButton
iconSide="right"
iconType="popout"
fill
isLoading={false}
type="submit"
href="https://ela.st/add-fleet-server"
target="_blank"
>
<FormattedMessage
id="xpack.fleet.fleetServerSetup.setupGuideLink"
defaultMessage="Fleet User Guide"
/>
</EuiButton>
}
</h2>
<EuiSpacer size="m" />
<FormattedMessage
id="xpack.fleet.fleetServerSetup.setupText"
defaultMessage="A Fleet Server is required before you can enroll agents with Fleet. See the {userGuideLink} for more information."
values={{
userGuideLink: (
<EuiLink href="https://ela.st/add-fleet-server" external>
<FormattedMessage
id="xpack.fleet.fleetServerSetup.setupGuideLink"
defaultMessage="Fleet User Guide"
/>
</EuiLink>
),
}}
/>
</EuiText>
<EuiSpacer size="l" />
<EuiSteps
className="eui-textLeft"
steps={[
DownloadStep(),
{
title: i18n.translate('xpack.fleet.fleetServerSetup.stepGenerateServiceTokenTitle', {
defaultMessage: 'Generate a service token',
}),
children: (
<>
<EuiText>
<FormattedMessage
id="xpack.fleet.fleetServerSetup.generateServiceTokenDescription"
defaultMessage="A service token grants Fleet Server permissions to write to Elasticsearch."
/>
</EuiText>
<EuiSpacer size="m" />
{!serviceToken ? (
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButton
fill
isLoading={isLoadingServiceToken}
isDisabled={isLoadingServiceToken}
onClick={() => {
getServiceToken();
}}
>
<FormattedMessage
id="xpack.fleet.fleetServerSetup.generateServiceTokenButton"
defaultMessage="Generate service token"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
) : (
<>
<EuiCallOut size="s">
<FormattedMessage
id="xpack.fleet.fleetServerSetup.saveServiceTokenDescription"
defaultMessage="Save your service token information. This will be shown only once."
/>
</EuiCallOut>
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<strong>
<FormattedMessage
id="xpack.fleet.fleetServerSetup.serviceTokenLabel"
defaultMessage="Service token"
/>
</strong>
</EuiFlexItem>
<FlexItemWithMinWidth>
<EuiCodeBlock paddingSize="m" isCopyable>
<CommandCode>{serviceToken}</CommandCode>
</EuiCodeBlock>
</FlexItemWithMinWidth>
</EuiFlexGroup>
</>
)}
</>
),
},
{
title: i18n.translate('xpack.fleet.fleetServerSetup.stepInstallAgentTitle', {
defaultMessage: 'Install the Elastic Agent as a Fleet Server',
}),
status: !serviceToken ? 'disabled' : undefined,
children: serviceToken ? (
<>
<EuiText>
<FormattedMessage
id="xpack.fleet.fleetServerSetup.installAgentDescription"
defaultMessage="From the agent directory, run the appropriate command to install, enroll, and start an Elastic Agent as a Fleet Server. Requires administrator privileges."
/>
</EuiText>
<EuiSpacer size="l" />
<EuiSelect
prepend={
<EuiText>
<FormattedMessage
id="xpack.fleet.fleetServerSetup.platformSelectLabel"
defaultMessage="Platform"
/>
</EuiText>
}
options={PLATFORM_OPTIONS}
value={platform}
onChange={(e) => setPlatform(e.target.value as PLATFORM_TYPE)}
aria-label={i18n.translate(
'xpack.fleet.fleetServerSetup.platformSelectAriaLabel',
{
defaultMessage: 'Platform',
}
)}
/>
<EuiSpacer size="s" />
<EuiCodeBlock
fontSize="m"
isCopyable={true}
paddingSize="m"
language="console"
whiteSpace="pre"
>
<CommandCode>{installCommand}</CommandCode>
</EuiCodeBlock>
</>
) : null,
},
]}
/>
</EuiPanel>
);
}
};

function renderCloudInstructions(deploymentUrl: string) {
const CloudInstructions: React.FC<{ deploymentUrl: string }> = ({ deploymentUrl }) => {
return (
<EuiPanel
paddingSize="none"
Expand Down Expand Up @@ -126,19 +293,24 @@ function renderCloudInstructions(deploymentUrl: string) {
/>
</EuiPanel>
);
}
};

export const FleetServerRequirementPage = () => {
const startService = useStartServices();
const deploymentUrl = startService.cloud?.deploymentUrl;

return (
<>
<ContentWrapper justifyContent="center" alignItems="center">
<ContentWrapper gutterSize="l" justifyContent="center" alignItems="center" direction="column">
<FlexItemWithMinWidth grow={false}>
{deploymentUrl ? (
<CloudInstructions deploymentUrl={deploymentUrl} />
) : (
<OnPremInstructions />
)}
</FlexItemWithMinWidth>
<EuiFlexItem grow={false}>
{deploymentUrl ? renderCloudInstructions(deploymentUrl) : renderOnPremInstructions()}
<EuiSpacer size="l" />
<EuiFlexGroup gutterSize="s" alignItems="center" justifyContent="center">
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="m" />
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,12 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({

<EuiFlyoutBody
banner={
fleetServerHosts.length === 0 ? (
fleetServerHosts.length === 0 && mode === 'managed' ? (
<MissingFleetServerHostCallout onClose={onClose} />
) : undefined
}
>
{fleetServerHosts.length === 0 ? null : mode === 'managed' ? (
{fleetServerHosts.length === 0 && mode === 'managed' ? null : mode === 'managed' ? (
<ManagedInstructions agentPolicies={agentPolicies} />
) : (
<StandaloneInstructions agentPolicies={agentPolicies} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export {
PutSettingsResponse,
// API schemas - app
CheckPermissionsResponse,
GenerateServiceTokenResponse,
// EPM types
AssetReference,
AssetsGroupedByServiceByType,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class AgentUnenrollmentError extends IngestManagerError {}
export class AgentPolicyDeletionError extends IngestManagerError {}

export class FleetSetupError extends IngestManagerError {}
export class GenerateServiceTokenError extends IngestManagerError {}

export class ArtifactsClientError extends IngestManagerError {}
export class ArtifactsClientAccessDeniedError extends IngestManagerError {
Expand Down
Loading