[Fleet] Redesign agent flyout (#128381)

* [Fleet] Redesign and refactor agent flyout

* Further refactoring

* More refactoring and some bug fixing

* bug fixing

* Fix step tests + skip standalone tab tests

* Use test-subj for agent policy code block to fix cypress tests

* Fixing small issues on rendering

* Fix broken import; Fix button in incoming data comp

* Fix loading; add button in incoming data step

* Fix some failures

* Fix tests

* Fix checks

* Address code review comments; add retry logic to incoming data component

* Fix download link

* Check enrolled agents in last 10 minutes

* Filter out agents with data=false from incoming data

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Kyle Pollich <kyle.pollich@elastic.co>
This commit is contained in:
Cristina Amico 2022-04-11 18:11:27 +02:00 committed by GitHub
parent b239151646
commit 6e8d97948a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 1705 additions and 1114 deletions

View file

@ -10,6 +10,7 @@ import {
ADD_AGENT_BUTTON_TOP,
AGENT_FLYOUT_CLOSE_BUTTON,
STANDALONE_TAB,
AGENT_POLICY_CODE_BLOCK,
} from '../screens/fleet';
import { cleanupAgentPolicies, unenrollAgent } from '../tasks/cleanup';
import { verifyPolicy, verifyAgentPackage, navigateToTab } from '../tasks/fleet';
@ -60,12 +61,11 @@ describe('Fleet startup', () => {
cy.log('Create agent policy took: ' + (Date.now() - startTime) / 1000 + ' s');
agentPolicyId = xhr.response.body.item.id;
cy.getBySel('agentPolicyCreateStatusCallOut').contains('Agent policy created');
// verify create button changed to dropdown
cy.getBySel('agentPolicyDropdown');
// verify agent.yml code block has new policy id
cy.get('.euiCodeBlock__code').contains(`id: ${agentPolicyId}`);
cy.getBySel(AGENT_POLICY_CODE_BLOCK).contains(`id: ${agentPolicyId}`);
cy.getBySel(AGENT_FLYOUT_CLOSE_BUTTON).click();
@ -78,7 +78,6 @@ describe('Fleet startup', () => {
it('should create Fleet Server policy', () => {
cy.getBySel('createFleetServerPolicyBtn').click();
cy.getBySel('agentPolicyCreateStatusCallOut').contains('Agent policy created');
// verify policy is created and has fleet server and system package
verifyPolicy('Fleet Server policy 1', ['Fleet Server', 'System']);

View file

@ -9,6 +9,7 @@ export const ADD_AGENT_BUTTON = 'addAgentButton';
export const ADD_AGENT_BUTTON_TOP = 'addAgentBtnTop';
export const CREATE_POLICY_BUTTON = 'createPolicyBtn';
export const AGENT_FLYOUT_CLOSE_BUTTON = 'euiFlyoutCloseButton';
export const AGENT_POLICY_CODE_BLOCK = 'agentPolicyCodeBlock';
export const AGENTS_TAB = 'fleet-agents-tab';
export const AGENT_POLICIES_TAB = 'fleet-agent-policies-tab';

View file

@ -18,7 +18,6 @@ export function createAgentPolicy() {
cy.getBySel(ADD_AGENT_BUTTON_TOP).click();
cy.getBySel(STANDALONE_TAB).click();
cy.getBySel(CREATE_POLICY_BUTTON).click();
cy.getBySel('agentPolicyCreateStatusCallOut').contains('Agent policy created');
cy.getBySel(AGENT_FLYOUT_CLOSE_BUTTON).click();
}

View file

@ -203,7 +203,6 @@ export const FleetServerCommandStep = ({
windowsCommand={installCommand.windows}
linuxDebCommand={installCommand.deb}
linuxRpmCommand={installCommand.rpm}
troubleshootLink={docLinks.links.fleet.troubleshooting}
isK8s={false}
/>
</>
@ -290,36 +289,16 @@ export const useFleetServerInstructions = (policyId?: string) => {
};
const AgentPolicySelectionStep = ({
policyId,
selectedPolicy,
setPolicyId,
agentPolicies,
refreshAgentPolicies,
}: {
policyId?: string;
setPolicyId: (v: string) => void;
selectedPolicy?: AgentPolicy;
setPolicyId: (v?: string) => void;
agentPolicies: AgentPolicy[];
refreshAgentPolicies: () => void;
}): EuiStepProps => {
const { data, resendRequest: refreshAgentPolicies } = useGetAgentPolicies({ full: true });
const agentPolicies = useMemo(
() => (data ? data.items.filter((item) => policyHasFleetServer(item)) : []),
[data]
);
useEffect(() => {
// Select default value
if (agentPolicies.length && !policyId) {
setPolicyId(agentPolicies[0].id);
}
}, [agentPolicies, policyId, setPolicyId]);
const onChangeCallback = useCallback(
(key: string | undefined, policy?: AgentPolicy) => {
if (policy) {
refreshAgentPolicies();
}
setPolicyId(key!);
},
[setPolicyId, refreshAgentPolicies]
);
return {
title:
agentPolicies.length === 0
@ -335,9 +314,11 @@ const AgentPolicySelectionStep = ({
<SelectCreateAgentPolicy
agentPolicies={agentPolicies}
withKeySelection={false}
onAgentPolicyChange={onChangeCallback}
excludeFleetServer={false}
isFleetServerPolicy={true}
selectedPolicy={selectedPolicy}
setSelectedPolicyId={setPolicyId}
refreshAgentPolicies={refreshAgentPolicies}
/>
</>
),
@ -642,9 +623,28 @@ const CompleteStep = (): EuiStepProps => {
};
};
const findPolicyById = (policies: AgentPolicy[], id: string | undefined) => {
if (!id) return undefined;
return policies.find((p) => p.id === id);
};
export const OnPremInstructions: React.FC = () => {
const { notifications } = useStartServices();
const [policyId, setPolicyId] = useState<string | undefined>();
const { data, resendRequest: refreshAgentPolicies } = useGetAgentPolicies({ full: true });
const agentPolicies = useMemo(
() => (data ? data.items.filter((item) => policyHasFleetServer(item)) : []),
[data]
);
// Select default value
let defaultValue = '';
if (agentPolicies.length) {
defaultValue = agentPolicies[0].id;
}
const [policyId, setPolicyId] = useState<string | undefined>(defaultValue);
const selectedPolicy = findPolicyById(agentPolicies, policyId);
const {
serviceToken,
@ -720,7 +720,12 @@ export const OnPremInstructions: React.FC = () => {
<EuiSteps
className="eui-textLeft"
steps={[
AgentPolicySelectionStep({ policyId, setPolicyId }),
AgentPolicySelectionStep({
selectedPolicy,
setPolicyId,
agentPolicies,
refreshAgentPolicies,
}),
DownloadStep(true),
deploymentModeStep({ deploymentMode, setDeploymentMode }),
addFleetServerHostStep({ addFleetServerHost }),

View file

@ -85,6 +85,7 @@ export const AgentsApp: React.FunctionComponent = () => {
<EuiPortal>
<AgentEnrollmentFlyout
defaultMode="standalone"
isIntegrationFlow={true}
onClose={() => setIsEnrollmentFlyoutOpen(false)}
/>
</EuiPortal>

View file

@ -7,11 +7,7 @@
import { stringify, parse } from 'query-string';
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { Redirect, useLocation, useHistory } from 'react-router-dom';
import type {
CriteriaWithPagination,
EuiStepProps,
EuiTableFieldDataColumnType,
} from '@elastic/eui';
import type { CriteriaWithPagination, EuiTableFieldDataColumnType } from '@elastic/eui';
import {
EuiBasicTable,
EuiLink,
@ -19,7 +15,6 @@ import {
EuiFlexItem,
EuiText,
EuiButton,
EuiSpacer,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedRelative, FormattedMessage } from '@kbn/i18n-react';
@ -31,7 +26,6 @@ import {
useUrlPagination,
useGetPackageInstallStatus,
AgentPolicyRefreshContext,
useUIExtension,
usePackageInstallations,
useAuthz,
} from '../../../../../hooks';
@ -103,7 +97,6 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: ${name}`,
});
const { updatableIntegrations } = usePackageInstallations();
const agentEnrollmentFlyoutExtension = useUIExtension(name, 'agent-enrollment-flyout');
const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
@ -165,50 +158,6 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
[setPagination]
);
const viewDataStep = useMemo<EuiStepProps>(() => {
if (agentEnrollmentFlyoutExtension) {
return {
title: agentEnrollmentFlyoutExtension.title,
children: <agentEnrollmentFlyoutExtension.Component />,
};
}
return {
title: i18n.translate('xpack.fleet.agentEnrollment.stepViewDataTitle', {
defaultMessage: 'View your data',
}),
children: (
<>
<EuiText>
<FormattedMessage
id="xpack.fleet.agentEnrollment.viewDataDescription"
defaultMessage="After your agent starts, you can view your data in Kibana by using the integration's installed assets. {pleaseNote}: it may take a few minutes for the initial data to arrive."
values={{
pleaseNote: (
<strong>
{i18n.translate(
'xpack.fleet.epm.agentEnrollment.viewDataDescription.pleaseNoteLabel',
{ defaultMessage: 'Please note' }
)}
</strong>
),
}}
/>
</EuiText>
<EuiSpacer size="l" />
<EuiButton
fill
href={getHref('integration_details_assets', { pkgkey: `${name}-${version}` })}
>
{i18n.translate('xpack.fleet.epm.agentEnrollment.viewDataAssetsLabel', {
defaultMessage: 'View assets',
})}
</EuiButton>
</>
),
};
}, [name, version, getHref, agentEnrollmentFlyoutExtension]);
const columns: Array<EuiTableFieldDataColumnType<InMemoryPackagePolicyAndAgentPolicy>> = useMemo(
() => [
{
@ -323,7 +272,6 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
<PackagePolicyActionsMenu
agentPolicy={agentPolicy}
packagePolicy={packagePolicy}
viewDataStep={viewDataStep}
showAddAgent={true}
upgradePackagePolicyHref={`${getHref('upgrade_package_policy', {
policyId: agentPolicy.id,
@ -334,7 +282,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
},
},
],
[getHref, showAddAgentHelpForPackagePolicyId, viewDataStep, canWriteIntegrationPolicies]
[getHref, showAddAgentHelpForPackagePolicyId, canWriteIntegrationPolicies]
);
const noItemsMessage = useMemo(() => {
@ -362,6 +310,11 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
<Redirect to={getPath('integration_details_overview', { pkgkey: `${name}-${version}` })} />
);
}
const selectedPolicies = packageAndAgentPolicies.find(
({ agentPolicy: policy }) => policy.id === flyoutOpenForPolicyId
);
const agentPolicy = selectedPolicies?.agentPolicy;
const packagePolicy = selectedPolicies?.packagePolicy;
return (
<AgentPolicyRefreshContext.Provider value={{ refresh: refreshPolicies }}>
@ -379,19 +332,19 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
/>
</EuiFlexItem>
</EuiFlexGroup>
{flyoutOpenForPolicyId && !isLoading && (
{flyoutOpenForPolicyId && agentPolicy && !isLoading && (
<AgentEnrollmentFlyout
onClose={() => {
setFlyoutOpenForPolicyId(null);
const { addAgentToPolicyId, ...rest } = parse(search);
history.replace({ search: stringify(rest) });
}}
agentPolicy={
packageAndAgentPolicies.find(
({ agentPolicy }) => agentPolicy.id === flyoutOpenForPolicyId
)?.agentPolicy
}
viewDataStep={viewDataStep}
agentPolicy={agentPolicy}
isIntegrationFlow={true}
installedPackagePolicy={{
name: packagePolicy?.package?.name || '',
version: packagePolicy?.package?.version || '',
}}
/>
)}
</AgentPolicyRefreshContext.Provider>

View file

@ -8,6 +8,7 @@
jest.mock('../../hooks', () => {
return {
...jest.requireActual('../../hooks'),
useFleetStatus: jest.fn(),
useAgentEnrollmentFlyoutData: jest.fn(),
};
});
@ -24,6 +25,16 @@ jest.mock('../../hooks/use_request', () => {
};
});
jest.mock('../../applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy', () => {
const module = jest.requireActual(
'../../applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy'
);
return {
...module,
useFleetServerUnhealthy: jest.fn(),
};
});
jest.mock(
'../../applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions',
() => {
@ -57,18 +68,17 @@ jest.mock('./steps', () => {
const module = jest.requireActual('./steps');
return {
...module,
AgentPolicySelectionStep: jest.fn(),
AgentEnrollmentKeySelectionStep: jest.fn(),
ViewDataStep: jest.fn(),
DownloadStep: jest.fn(),
};
});
jest.mock('@elastic/eui', () => {
const module = jest.requireActual('@elastic/eui');
return {
...module,
EuiSteps: 'eui-steps',
AgentPolicySelectionStep: jest.fn().mockReturnValue({
'data-test-subj': 'agent-policy-selection-step',
title: 'agent-policy-selection-step',
}),
AgentEnrollmentKeySelectionStep: jest.fn().mockReturnValue({
'data-test-subj': 'agent-enrollment-key-selection-step',
title: 'agent-enrollment-key-selection-step',
}),
DownloadStep: jest
.fn()
.mockReturnValue({ 'data-test-subj': 'download-step', title: 'download-step' }),
};
});

View file

@ -16,27 +16,23 @@ import { coreMock } from 'src/core/public/mocks';
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
import type { AgentPolicy } from '../../../common';
import {
useGetSettings,
sendGetFleetStatus,
sendGetOneAgentPolicy,
useGetAgents,
} from '../../hooks/use_request';
import { useGetSettings, sendGetOneAgentPolicy, useGetAgents } from '../../hooks/use_request';
import {
FleetStatusProvider,
ConfigContext,
useAgentEnrollmentFlyoutData,
KibanaVersionContext,
useFleetStatus,
} from '../../hooks';
import { useFleetServerInstructions } from '../../applications/fleet/sections/agents/agent_requirements_page/components';
import { AgentEnrollmentKeySelectionStep, AgentPolicySelectionStep } from './steps';
import { useFleetServerUnhealthy } from '../../applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy';
import type { Props } from '.';
import type { FlyOutProps } from './types';
import { AgentEnrollmentFlyout } from '.';
const TestComponent = (props: Props) => (
const TestComponent = (props: FlyOutProps) => (
<KibanaContextProvider services={coreMock.createStart()}>
<ConfigContext.Provider value={{ agents: { enabled: true, elasticsearch: {} }, enabled: true }}>
<KibanaVersionContext.Provider value={'8.1.0'}>
@ -48,7 +44,7 @@ const TestComponent = (props: Props) => (
</KibanaContextProvider>
);
const setup = async (props?: Props) => {
const setup = async (props?: FlyOutProps) => {
const testBed = await registerTestBed(TestComponent)(props);
const { find, component } = testBed;
@ -87,8 +83,10 @@ describe('<AgentEnrollmentFlyout />', () => {
data: { item: { fleet_server_hosts: ['test'] } },
});
(sendGetFleetStatus as jest.Mock).mockResolvedValue({
data: { isReady: true },
(useFleetStatus as jest.Mock).mockReturnValue({ isReady: true });
(useFleetServerUnhealthy as jest.Mock).mockReturnValue({
isLoading: false,
isUnhealthy: false,
});
(sendGetOneAgentPolicy as jest.Mock).mockResolvedValue({
@ -147,9 +145,10 @@ describe('<AgentEnrollmentFlyout />', () => {
describe('managed instructions', () => {
it('uses the agent policy selection step', async () => {
const { exists } = testBed;
expect(exists('agentEnrollmentFlyout')).toBe(true);
expect(AgentPolicySelectionStep).toHaveBeenCalled();
expect(AgentEnrollmentKeySelectionStep).not.toHaveBeenCalled();
expect(exists('agent-policy-selection-step')).toBe(true);
expect(exists('agent-enrollment-key-selection-step')).toBe(false);
});
describe('with a specific policy', () => {
@ -167,8 +166,8 @@ describe('<AgentEnrollmentFlyout />', () => {
it('uses the configure enrollment step, not the agent policy selection step', () => {
const { exists } = testBed;
expect(exists('agentEnrollmentFlyout')).toBe(true);
expect(AgentPolicySelectionStep).not.toHaveBeenCalled();
expect(AgentEnrollmentKeySelectionStep).toHaveBeenCalled();
expect(exists('agent-policy-selection-step')).toBe(false);
expect(exists('agent-enrollment-key-selection-step')).toBe(true);
});
});
@ -187,58 +186,21 @@ describe('<AgentEnrollmentFlyout />', () => {
it('should not show fleet server instructions', () => {
const { exists } = testBed;
expect(exists('agentEnrollmentFlyout')).toBe(true);
expect(AgentEnrollmentKeySelectionStep).toHaveBeenCalled();
});
});
// Skipped due to implementation details in the step components. See https://github.com/elastic/kibana/issues/103894
describe.skip('"View data" extension point', () => {
it('shows the "View data" step when UI extension is provided', async () => {
jest.clearAllMocks();
await act(async () => {
testBed = await setup({
onClose: jest.fn(),
viewDataStep: { title: 'View Data', children: <div /> },
});
testBed.component.update();
});
const { exists, actions } = testBed;
expect(exists('agentEnrollmentFlyout')).toBe(true);
expect(exists('view-data-step')).toBe(true);
jest.clearAllMocks();
actions.goToStandaloneTab();
expect(exists('agentEnrollmentFlyout')).toBe(true);
expect(exists('view-data-step')).toBe(false);
});
it('does not call the "View data" step when UI extension is not provided', async () => {
jest.clearAllMocks();
await act(async () => {
testBed = await setup({
onClose: jest.fn(),
viewDataStep: undefined,
});
testBed.component.update();
});
const { exists, actions } = testBed;
expect(exists('agentEnrollmentFlyout')).toBe(true);
expect(exists('view-data-step')).toBe(false);
jest.clearAllMocks();
actions.goToStandaloneTab();
expect(exists('view-data-step')).toBe(false);
expect(exists('agent-enrollment-key-selection-step')).toBe(true);
});
});
});
describe('standalone instructions', () => {
// Skipped due to UI changing in https://github.com/elastic/kibana/issues/125534. These tests should be rethought overall
// to provide value around the new flyout structure
describe.skip('standalone instructions', () => {
it('uses the agent policy selection step', async () => {
const { exists, actions } = testBed;
actions.goToStandaloneTab();
expect(exists('agentEnrollmentFlyout')).toBe(true);
expect(AgentPolicySelectionStep).toHaveBeenCalled();
expect(AgentEnrollmentKeySelectionStep).not.toHaveBeenCalled();
expect(exists('agent-policy-selection-step')).toBe(true);
expect(exists('agent-enrollment-key-selection-step')).toBe(false);
});
describe('with a specific policy', () => {
@ -256,10 +218,12 @@ describe('<AgentEnrollmentFlyout />', () => {
it('does not use either of the agent policy selection or enrollment key steps', () => {
const { exists, actions } = testBed;
jest.clearAllMocks();
expect(exists('agentEnrollmentFlyout')).toBe(true);
actions.goToStandaloneTab();
expect(AgentPolicySelectionStep).not.toHaveBeenCalled();
expect(AgentEnrollmentKeySelectionStep).not.toHaveBeenCalled();
expect(exists('agentEnrollmentFlyout')).toBe(true);
expect(exists('agent-policy-selection-step')).toBe(false);
expect(exists('agent-enrollment-key-selection-step')).toBe(false);
});
});
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useState, useCallback, useEffect } from 'react';
import React, { useState, useCallback, useEffect, useMemo } from 'react';
import type { AgentPolicyCreateState } from '../../applications/fleet/sections/agents/components';
import {
@ -16,43 +16,60 @@ import { AgentPolicyCreateInlineForm } from '../../applications/fleet/sections/a
import type { AgentPolicy } from '../../types';
import { incrementPolicyName } from '../../services';
import { EnrollmentStepAgentPolicy } from '.';
import { AgentPolicySelection } from '.';
interface Props {
agentPolicies: AgentPolicy[];
selectedPolicy?: AgentPolicy;
setSelectedPolicyId: (agentPolicyId?: string) => void;
excludeFleetServer?: boolean;
onAgentPolicyChange: (key?: string, policy?: AgentPolicy) => void;
withKeySelection: boolean;
selectedApiKeyId?: string;
onKeyChange?: (key?: string) => void;
isFleetServerPolicy?: boolean;
policyId?: string;
refreshAgentPolicies: () => void;
}
export const SelectCreateAgentPolicy: React.FC<Props> = ({
agentPolicies,
excludeFleetServer,
onAgentPolicyChange,
setSelectedPolicyId,
selectedPolicy,
withKeySelection,
selectedApiKeyId,
onKeyChange,
isFleetServerPolicy,
policyId,
refreshAgentPolicies,
}) => {
const [showCreatePolicy, setShowCreatePolicy] = useState(agentPolicies.length === 0);
const regularAgentPolicies = useMemo(() => {
return agentPolicies.filter(
(policy) =>
policy && !policy.is_managed && (!excludeFleetServer || !policy.is_default_fleet_server)
);
}, [agentPolicies, excludeFleetServer]);
const onAgentPolicyChange = useCallback(
async (key?: string, policy?: AgentPolicy) => {
if (policy) {
refreshAgentPolicies();
}
},
[refreshAgentPolicies]
);
const [showCreatePolicy, setShowCreatePolicy] = useState(regularAgentPolicies.length === 0);
const [createState, setCreateState] = useState<AgentPolicyCreateState>({
status: CREATE_STATUS.INITIAL,
});
const [newName, setNewName] = useState(incrementPolicyName(agentPolicies, isFleetServerPolicy));
const [selectedAgentPolicy, setSelectedAgentPolicy] = useState<string | undefined>(policyId);
const [newName, setNewName] = useState(
incrementPolicyName(regularAgentPolicies, isFleetServerPolicy)
);
useEffect(() => {
setShowCreatePolicy(agentPolicies.length === 0);
setNewName(incrementPolicyName(agentPolicies, isFleetServerPolicy));
}, [agentPolicies, isFleetServerPolicy]);
setShowCreatePolicy(regularAgentPolicies.length === 0);
setNewName(incrementPolicyName(regularAgentPolicies, isFleetServerPolicy));
}, [regularAgentPolicies, isFleetServerPolicy]);
const onAgentPolicyCreated = useCallback(
async (policy: AgentPolicy | null, errorMessage?: JSX.Element) => {
@ -65,9 +82,9 @@ export const SelectCreateAgentPolicy: React.FC<Props> = ({
if (onAgentPolicyChange) {
onAgentPolicyChange(policy.id, policy!);
}
setSelectedAgentPolicy(policy.id);
setSelectedPolicyId(policy.id);
},
[onAgentPolicyChange]
[setSelectedPolicyId, onAgentPolicyChange]
);
const onClickCreatePolicy = () => {
@ -87,15 +104,15 @@ export const SelectCreateAgentPolicy: React.FC<Props> = ({
agentPolicyName={newName}
/>
) : (
<EnrollmentStepAgentPolicy
agentPolicies={agentPolicies}
<AgentPolicySelection
agentPolicies={regularAgentPolicies}
withKeySelection={withKeySelection}
selectedApiKeyId={selectedApiKeyId}
onKeyChange={onKeyChange}
onAgentPolicyChange={onAgentPolicyChange}
excludeFleetServer={excludeFleetServer}
onClickCreatePolicy={onClickCreatePolicy}
selectedAgentPolicy={selectedAgentPolicy}
selectedPolicy={selectedPolicy}
setSelectedPolicyId={setSelectedPolicyId}
isFleetServerPolicy={isFleetServerPolicy}
/>
)}

View file

@ -13,7 +13,7 @@ import { createFleetTestRendererMock } from '../../mock';
import type { AgentPolicy } from '../../types';
import { EnrollmentStepAgentPolicy } from '.';
import { AgentPolicySelection } from '.';
describe('step select agent policy', () => {
let testRenderer: TestRenderer;
@ -21,10 +21,11 @@ describe('step select agent policy', () => {
let agentPolicies: AgentPolicy[] = [];
const render = () =>
(renderResult = testRenderer.render(
<EnrollmentStepAgentPolicy
<AgentPolicySelection
setSelectedPolicyId={jest.fn()}
selectedPolicy={agentPolicies[0]}
agentPolicies={agentPolicies}
withKeySelection={false}
onAgentPolicyChange={jest.fn()}
excludeFleetServer={true}
onClickCreatePolicy={jest.fn()}
isFleetServerPolicy={false}
@ -35,7 +36,7 @@ describe('step select agent policy', () => {
testRenderer = createFleetTestRendererMock();
});
test('should not select agent policy by default if multiple exists', async () => {
test('should select first agent policy by default if multiple exists', async () => {
agentPolicies = [
{ id: 'policy-1', name: 'Policy 1' } as AgentPolicy,
{ id: 'policy-2', name: 'Policy 2' } as AgentPolicy,
@ -45,7 +46,7 @@ describe('step select agent policy', () => {
await act(async () => {
const select = renderResult.container.querySelector('[data-test-subj="agentPolicyDropdown"]');
expect((select as any)?.value).toEqual('');
expect((select as any)?.value).toEqual('policy-1');
expect(renderResult.getAllByRole('option').length).toBe(2);
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useState, useEffect } from 'react';
import React, { useEffect } from 'react';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
@ -34,10 +34,10 @@ const AgentPolicyFormRow = styled(EuiFormRow)`
type Props = {
agentPolicies: AgentPolicy[];
onAgentPolicyChange: (key?: string, policy?: AgentPolicy) => void;
selectedPolicy?: AgentPolicy;
setSelectedPolicyId: (agentPolicyId?: string) => void;
excludeFleetServer?: boolean;
onClickCreatePolicy: () => void;
selectedAgentPolicy?: string;
isFleetServerPolicy?: boolean;
} & (
| {
@ -63,44 +63,33 @@ const resolveAgentId = (
return selectedAgentPolicyId;
};
export const EnrollmentStepAgentPolicy: React.FC<Props> = (props) => {
export const AgentPolicySelection: React.FC<Props> = (props) => {
const {
agentPolicies,
onAgentPolicyChange,
selectedPolicy,
setSelectedPolicyId,
excludeFleetServer,
onClickCreatePolicy,
selectedAgentPolicy,
isFleetServerPolicy,
} = props;
const [selectedAgentPolicyId, setSelectedAgentPolicyId] = useState<undefined | string>(
() => resolveAgentId(agentPolicies, undefined) // no agent id selected yet
);
const hasFleetAllPrivileges = useAuthz().fleet.all;
useEffect(
function triggerOnAgentPolicyChangeEffect() {
if (onAgentPolicyChange) {
onAgentPolicyChange(selectedAgentPolicyId);
}
},
[selectedAgentPolicyId, onAgentPolicyChange]
);
useEffect(
function useDefaultAgentPolicyEffect() {
const resolvedId = resolveAgentId(agentPolicies, selectedAgentPolicyId);
if (resolvedId !== selectedAgentPolicyId) {
setSelectedAgentPolicyId(resolvedId);
const resolvedId = resolveAgentId(agentPolicies, selectedPolicy?.id);
// find AgentPolicy
if (resolvedId !== selectedPolicy?.id) {
setSelectedPolicyId(resolvedId);
}
},
[agentPolicies, selectedAgentPolicyId]
[agentPolicies, setSelectedPolicyId, selectedPolicy]
);
useEffect(() => {
if (selectedAgentPolicy) setSelectedAgentPolicyId(selectedAgentPolicy);
}, [selectedAgentPolicy]);
const onChangeCallback = (event: React.ChangeEvent<HTMLSelectElement>) => {
const { value } = event.target;
setSelectedPolicyId(value);
};
return (
<>
@ -154,24 +143,24 @@ export const EnrollmentStepAgentPolicy: React.FC<Props> = (props) => {
value: agentPolicy.id,
text: agentPolicy.name,
}))}
value={selectedAgentPolicyId}
onChange={(e) => setSelectedAgentPolicyId(e.target.value)}
value={selectedPolicy?.id}
onChange={onChangeCallback}
aria-label={i18n.translate(
'xpack.fleet.enrollmentStepAgentPolicy.policySelectAriaLabel',
{
defaultMessage: 'Agent policy',
}
)}
hasNoInitialSelection={!selectedAgentPolicyId}
hasNoInitialSelection={!selectedPolicy?.id}
data-test-subj="agentPolicyDropdown"
isInvalid={!selectedAgentPolicyId}
isInvalid={!selectedPolicy?.id}
/>
</AgentPolicyFormRow>
{selectedAgentPolicyId && !isFleetServerPolicy && (
{selectedPolicy?.id && !isFleetServerPolicy && (
<>
<EuiSpacer size="m" />
<AgentPolicyPackageBadges
agentPolicyId={selectedAgentPolicyId}
agentPolicyId={selectedPolicy?.id}
excludeFleetServer={excludeFleetServer}
/>
</>
@ -183,7 +172,7 @@ export const EnrollmentStepAgentPolicy: React.FC<Props> = (props) => {
selectedApiKeyId={props.selectedApiKeyId}
onKeyChange={props.onKeyChange}
initialAuthenticationSettingsOpen={!props.selectedApiKeyId}
agentPolicyId={selectedAgentPolicyId}
agentPolicyId={selectedPolicy?.id}
/>
</>
)}

View file

@ -5,29 +5,99 @@
* 2.0.
*/
import React from 'react';
import { EuiCallOut, EuiButton } from '@elastic/eui';
import React, { useEffect, useRef, useState } from 'react';
import { EuiCallOut, EuiButton, EuiText, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { useGetAgentStatus } from '../../hooks';
import { sendGetAgents, useLink, useStartServices } from '../../hooks';
import { AGENTS_PREFIX } from '../../constants';
interface Props {
policyId: string;
onClickViewAgents: () => void;
policyId?: string;
troubleshootLink: string;
onClickViewAgents?: () => void;
agentCount: number;
}
const POLLING_INTERVAL_MS = 5 * 1000; // 5 sec
/**
* Hook for finding agents enrolled since component was rendered. Should be
* used by parent component to power rendering
* @param policyId
* @returns agentIds
*/
export const usePollingAgentCount = (policyId: string) => {
const [agentIds, setAgentIds] = useState<string[]>([]);
const timeout = useRef<number | undefined>(undefined);
useEffect(() => {
let isAborted = false;
const poll = () => {
timeout.current = window.setTimeout(async () => {
const request = await sendGetAgents({
kuery: `${AGENTS_PREFIX}.policy_id:"${policyId}" and ${AGENTS_PREFIX}.enrolled_at >= now-10m`,
showInactive: false,
});
const newAgentIds = request.data?.items.map((i) => i.id) ?? agentIds;
if (newAgentIds.some((id) => !agentIds.includes(id))) {
setAgentIds(newAgentIds);
}
if (!isAborted) {
poll();
}
}, POLLING_INTERVAL_MS);
};
poll();
if (isAborted || agentIds.length > 0) clearTimeout(timeout.current);
return () => {
isAborted = true;
};
}, [agentIds, policyId]);
return agentIds;
};
export const ConfirmAgentEnrollment: React.FunctionComponent<Props> = ({
policyId,
troubleshootLink,
onClickViewAgents,
agentCount,
}) => {
// Check the agents enrolled in the last 10 minutes
const enrolledAt = 'now-10m';
const kuery = `${AGENTS_PREFIX}.enrolled_at >= "${enrolledAt}"`;
const agentStatusRequest = useGetAgentStatus({ kuery, policyId });
const agentsCount = agentStatusRequest.data?.results?.total;
const { getHref } = useLink();
const { application } = useStartServices();
if (!agentsCount) {
return null;
const onButtonClick = () => {
if (onClickViewAgents) onClickViewAgents();
const href = getHref('agent_list');
application.navigateToUrl(href);
};
if (!policyId || agentCount === 0) {
return (
<EuiText>
<FormattedMessage
data-test-subj="ConfirmAgentEnrollmentCallOut.troubleshooting"
id="xpack.fleet.enrollmentInstructions.troubleshootingText"
defaultMessage="If you are having trouble connecting, see our {link}."
values={{
link: (
<EuiLink target="_blank" external href={troubleshootLink}>
<FormattedMessage
id="xpack.fleet.enrollmentInstructions.troubleshootingLink"
defaultMessage="troubleshooting guide"
/>
</EuiLink>
),
}}
/>
</EuiText>
);
}
return (
@ -35,16 +105,16 @@ export const ConfirmAgentEnrollment: React.FunctionComponent<Props> = ({
data-test-subj="ConfirmAgentEnrollmentCallOut"
title={i18n.translate('xpack.fleet.agentEnrollment.confirmation.title', {
defaultMessage:
'{agentsCount} {agentsCount, plural, one {agent has} other {agents have}} been enrolled.',
'{agentCount} {agentCount, plural, one {agent has} other {agents have}} been enrolled.',
values: {
agentsCount,
agentCount,
},
})}
color="success"
iconType="check"
>
<EuiButton
onClick={onClickViewAgents}
onClick={onButtonClick}
color="success"
data-test-subj="ConfirmAgentEnrollmentButton"
>

View file

@ -9,66 +9,73 @@ import React from 'react';
import { EuiCallOut, EuiText, EuiSpacer, EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { InstalledIntegrationPolicy } from '../../hooks';
import { useGetAgentIncomingData } from '../../hooks';
import type { InstalledIntegrationPolicy } from './use_get_agent_incoming_data';
import { useGetAgentIncomingData, usePollingIncomingData } from './use_get_agent_incoming_data';
interface Props {
agentsIds: string[];
agentIds: string[];
installedPolicy?: InstalledIntegrationPolicy;
agentDataConfirmed: boolean;
setAgentDataConfirmed: (v: boolean) => void;
}
export const ConfirmIncomingData: React.FunctionComponent<Props> = ({
agentsIds,
agentIds,
installedPolicy,
agentDataConfirmed,
setAgentDataConfirmed,
}) => {
const { enrolledAgents, numAgentsWithData, isLoading, linkButton } = useGetAgentIncomingData(
agentsIds,
const { incomingData, isLoading } = usePollingIncomingData(agentIds);
const { enrolledAgents, numAgentsWithData, linkButton, message } = useGetAgentIncomingData(
incomingData,
installedPolicy
);
if (!isLoading && enrolledAgents > 0 && numAgentsWithData > 0) {
setAgentDataConfirmed(true);
}
if (!agentDataConfirmed) {
return (
<EuiText size="s">
{i18n.translate('xpack.fleet.confirmIncomingData.loading', {
defaultMessage:
'It may take a few minutes for data to arrive in Elasticsearch. If the system is not generating data, it may help to generate some to ensure data is being collected correctly. If youre having trouble, see our troubleshooting guide. You may close this dialog and check later by viewing our integration assets.',
})}
</EuiText>
);
}
return (
<>
{isLoading ? (
<EuiText size="s">
{i18n.translate('xpack.fleet.confirmIncomingData.loading', {
defaultMessage:
'It may take a few minutes for data to arrive in Elasticsearch. If the system is not generating data, it may help to generate some to ensure data is being collected correctly. If youre having trouble, see our troubleshooting guide. You may close this dialog and check later by viewing our integration assets.',
})}
</EuiText>
) : (
<>
<EuiCallOut
data-test-subj="IncomingDataConfirmedCallOut"
title={i18n.translate('xpack.fleet.confirmIncomingData.title', {
defaultMessage:
'Incoming data received from {numAgentsWithData} of {enrolledAgents} recently enrolled { enrolledAgents, plural, one {agent} other {agents}}.',
values: {
numAgentsWithData,
enrolledAgents,
},
})}
color="success"
iconType="check"
/>
<EuiSpacer size="m" />
<EuiText size="s">
{i18n.translate('xpack.fleet.confirmIncomingData.subtitle', {
defaultMessage: 'Your agent is enrolled successfully and your data is received.',
})}
</EuiText>
</>
)}
<EuiSpacer size="m" />
<EuiCallOut
data-test-subj="IncomingDataConfirmedCallOut"
title={i18n.translate('xpack.fleet.confirmIncomingData.title', {
defaultMessage:
'Incoming data received from {numAgentsWithData} of {enrolledAgents} recently enrolled { enrolledAgents, plural, one {agent} other {agents}}.',
values: {
numAgentsWithData,
enrolledAgents,
},
})}
color="success"
iconType="check"
/>
{installedPolicy && (
<EuiButton
href={linkButton.href}
isDisabled={isLoading}
color="primary"
fill
data-test-subj="IncomingDataConfirmedButton"
>
{linkButton.text}
</EuiButton>
<>
<EuiSpacer size="m" />
<EuiText size="s">{message}</EuiText>
<EuiSpacer size="m" />
<EuiButton
href={linkButton.href}
isDisabled={isLoading}
color="primary"
fill
data-test-subj="IncomingDataConfirmedButton"
>
{linkButton.text}
</EuiButton>
</>
)}
</>
);

View file

@ -0,0 +1,35 @@
/*
* 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 { EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useLink } from '../../hooks';
export const DefaultMissingRequirements = () => {
const { getHref } = useLink();
return (
<>
<FormattedMessage
id="xpack.fleet.agentEnrollment.agentsNotInitializedText"
defaultMessage="Before enrolling agents, {link}."
values={{
link: (
<EuiLink href={getHref('overview')}>
<FormattedMessage
id="xpack.fleet.agentEnrollment.setUpAgentsLink"
defaultMessage="set up central management for Elastic Agents"
/>
</EuiLink>
),
}}
/>
</>
);
};

View file

@ -9,8 +9,11 @@ import { i18n } from '@kbn/i18n';
import type { PackagePolicy, AgentPolicy } from '../../types';
import { sendGetOneAgentPolicy, useStartServices } from '../../hooks';
import { FLEET_KUBERNETES_PACKAGE } from '../../../common';
import type { K8sMode } from './types';
export function useAgentPolicyWithPackagePolicies(policyId?: string) {
const [agentPolicyWithPackagePolicies, setAgentPolicy] = useState<AgentPolicy | null>(null);
const core = useStartServices();
@ -41,9 +44,7 @@ export function useAgentPolicyWithPackagePolicies(policyId?: string) {
}
export function useIsK8sPolicy(agentPolicy?: AgentPolicy) {
const [isK8s, setIsK8s] = useState<'IS_LOADING' | 'IS_KUBERNETES' | 'IS_NOT_KUBERNETES'>(
'IS_LOADING'
);
const [isK8s, setIsK8s] = useState<K8sMode>('IS_LOADING');
useEffect(() => {
async function checkifK8s() {
if (!agentPolicy) {

View file

@ -24,43 +24,42 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { useGetSettings, useFleetStatus, useAgentEnrollmentFlyoutData } from '../../hooks';
import { FLEET_SERVER_PACKAGE } from '../../constants';
import type { PackagePolicy } from '../../types';
import type { PackagePolicy, AgentPolicy } from '../../types';
import { Loading } from '..';
import { ManagedInstructions } from './managed_instructions';
import { StandaloneInstructions } from './standalone_instructions';
import { Instructions } from './instructions';
import { MissingFleetServerHostCallout } from './missing_fleet_server_host_callout';
import type { BaseProps } from './types';
import type { FlyOutProps, SelectionType, FlyoutMode } from './types';
import { useIsK8sPolicy, useAgentPolicyWithPackagePolicies } from './hooks';
type FlyoutMode = 'managed' | 'standalone';
export interface Props extends BaseProps {
onClose: () => void;
defaultMode?: FlyoutMode;
}
export * from './agent_policy_selection';
export * from './agent_policy_select_create';
export * from './managed_instructions';
export * from './standalone_instructions';
export * from './instructions';
export * from './steps';
export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({
export const AgentEnrollmentFlyout: React.FunctionComponent<FlyOutProps> = ({
onClose,
agentPolicy,
viewDataStep,
defaultMode = 'managed',
isIntegrationFlow,
installedPackagePolicy,
}) => {
const [mode, setMode] = useState<FlyoutMode>(defaultMode);
const findPolicyById = (policies: AgentPolicy[], id: string | undefined) => {
if (!id) return undefined;
return policies.find((p) => p.id === id);
};
const settings = useGetSettings();
const fleetStatus = useFleetStatus();
const fleetServerHosts = settings.data?.item?.fleet_server_hosts || [];
const fleetStatus = useFleetStatus();
const [policyId, setSelectedPolicyId] = useState(agentPolicy?.id);
const [selectedPolicyId, setSelectedPolicyId] = useState(agentPolicy?.id);
const [isFleetServerPolicySelected, setIsFleetServerPolicySelected] = useState<boolean>(false);
const [selectedApiKeyId, setSelectedAPIKeyId] = useState<string | undefined>();
const [mode, setMode] = useState<FlyoutMode>(defaultMode);
const [selectionType, setSelectionType] = useState<SelectionType>('tabs');
const {
agentPolicies,
@ -68,11 +67,19 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({
isLoadingAgentPolicies,
refreshAgentPolicies,
} = useAgentEnrollmentFlyoutData();
const { agentPolicyWithPackagePolicies } = useAgentPolicyWithPackagePolicies(policyId);
const { agentPolicyWithPackagePolicies } = useAgentPolicyWithPackagePolicies(selectedPolicyId);
const selectedPolicy = agentPolicyWithPackagePolicies
? agentPolicyWithPackagePolicies
: findPolicyById(agentPolicies, selectedPolicyId);
const hasNoFleetServerHost = fleetStatus.isReady && fleetServerHosts.length === 0;
useEffect(() => {
if (agentPolicyWithPackagePolicies && setIsFleetServerPolicySelected) {
if (selectedPolicy) {
if (
(agentPolicyWithPackagePolicies.package_policies as PackagePolicy[]).some(
(selectedPolicy.package_policies as PackagePolicy[]).some(
(packagePolicy) => packagePolicy.package?.name === FLEET_SERVER_PACKAGE
)
) {
@ -81,11 +88,9 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({
setIsFleetServerPolicySelected(false);
}
}
}, [agentPolicyWithPackagePolicies]);
}, [selectedPolicy, isFleetServerPolicySelected]);
const { isK8s } = useIsK8sPolicy(
agentPolicyWithPackagePolicies ? agentPolicyWithPackagePolicies : undefined
);
const { isK8s } = useIsK8sPolicy(selectedPolicy ? selectedPolicy : undefined);
const isLoadingInitialRequest = settings.isLoading && settings.isInitialRequest;
@ -107,61 +112,63 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({
defaultMessage="Add Elastic Agents to your hosts to collect data and send it to the Elastic Stack."
/>
</EuiText>
<EuiSpacer size="l" />
<EuiTabs style={{ marginBottom: '-25px' }}>
<EuiTab
data-test-subj="managedTab"
isSelected={mode === 'managed'}
onClick={() => setMode('managed')}
>
<FormattedMessage
id="xpack.fleet.agentEnrollment.enrollFleetTabLabel"
defaultMessage="Enroll in Fleet"
/>
</EuiTab>
<EuiTab
data-test-subj="standaloneTab"
isSelected={mode === 'standalone'}
onClick={() => setMode('standalone')}
>
<FormattedMessage
id="xpack.fleet.agentEnrollment.enrollStandaloneTabLabel"
defaultMessage="Run standalone"
/>
</EuiTab>
</EuiTabs>
{selectionType === 'tabs' ? (
<>
<EuiSpacer size="l" />
<EuiTabs style={{ marginBottom: '-25px' }}>
<EuiTab
data-test-subj="managedTab"
isSelected={mode === 'managed'}
onClick={() => setMode('managed')}
>
<FormattedMessage
id="xpack.fleet.agentEnrollment.enrollFleetTabLabel"
defaultMessage="Enroll in Fleet"
/>
</EuiTab>
<EuiTab
data-test-subj="standaloneTab"
isSelected={mode === 'standalone'}
onClick={() => setMode('standalone')}
>
<FormattedMessage
id="xpack.fleet.agentEnrollment.enrollStandaloneTabLabel"
defaultMessage="Run standalone"
/>
</EuiTab>
</EuiTabs>
</>
) : null}
</EuiFlyoutHeader>
<EuiFlyoutBody
banner={
fleetStatus.isReady &&
!isLoadingInitialRequest &&
fleetServerHosts.length === 0 &&
mode === 'managed' ? (
hasNoFleetServerHost && !isLoadingInitialRequest && mode === 'managed' ? (
<MissingFleetServerHostCallout />
) : undefined
}
>
{isLoadingInitialAgentPolicies ? (
<Loading />
) : mode === 'managed' ? (
<ManagedInstructions
<Loading size="l" />
) : (
<Instructions
settings={settings.data?.item}
setSelectedPolicyId={setSelectedPolicyId}
policyId={policyId}
agentPolicy={agentPolicy}
selectedPolicy={selectedPolicy}
agentPolicies={agentPolicies}
viewDataStep={viewDataStep}
isFleetServerPolicySelected={isFleetServerPolicySelected}
isK8s={isK8s}
refreshAgentPolicies={refreshAgentPolicies}
isLoadingAgentPolicies={isLoadingAgentPolicies}
/>
) : (
<StandaloneInstructions
agentPolicy={agentPolicy}
agentPolicies={agentPolicies}
refreshAgentPolicies={refreshAgentPolicies}
mode={mode}
setMode={setMode}
selectionType={selectionType}
setSelectionType={setSelectionType}
isIntegrationFlow={isIntegrationFlow}
selectedApiKeyId={selectedApiKeyId}
setSelectedAPIKeyId={setSelectedAPIKeyId}
onClickViewAgents={onClose}
installedPackagePolicy={installedPackagePolicy}
/>
)}
</EuiFlyoutBody>

View file

@ -0,0 +1,59 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useMemo } from 'react';
import { EuiText, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import semverMajor from 'semver/functions/major';
import semverMinor from 'semver/functions/minor';
import semverPatch from 'semver/functions/patch';
import { useKibanaVersion } from '../../hooks';
export const InstallationMessage: React.FunctionComponent = () => {
const kibanaVersion = useKibanaVersion();
const kibanaVersionURLString = useMemo(
() =>
`${semverMajor(kibanaVersion)}-${semverMinor(kibanaVersion)}-${semverPatch(kibanaVersion)}`,
[kibanaVersion]
);
return (
<EuiText>
<FormattedMessage
id="xpack.fleet.enrollmentInstructions.installationMessage"
defaultMessage="Select the appropriate platform and run commands to install, enroll, and start Elastic Agent. Reuse commands to set up agents on more than one host. For aarch64, see our {downloadLink}. For additional guidance, see our {installationLink}."
values={{
downloadLink: (
<EuiLink
target="_blank"
external
href={`https://www.elastic.co/downloads/past-releases/elastic-agent-${kibanaVersionURLString}`}
>
<FormattedMessage
id="xpack.fleet.enrollmentInstructions.downloadLink"
defaultMessage="downloads page"
/>
</EuiLink>
),
installationLink: (
<EuiLink
target="_blank"
external
href="https://www.elastic.co/guide/en/fleet/current/elastic-agent-installation.html"
>
<FormattedMessage
id="xpack.fleet.enrollmentInstructions.installationMessage.link"
defaultMessage="installation docs"
/>
</EuiLink>
),
}}
/>
</EuiText>
);
};

View file

@ -0,0 +1,126 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useMemo } from 'react';
import { EuiText, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useFleetStatus, useGetAgents } from '../../hooks';
import { FleetServerRequirementPage } from '../../applications/fleet/sections/agents/agent_requirements_page';
import { policyHasFleetServer } from '../../applications/fleet/sections/agents/services/has_fleet_server';
import { FLEET_SERVER_PACKAGE } from '../../constants';
import { useFleetServerUnhealthy } from '../../applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy';
import { Loading } from '..';
import type { InstructionProps } from './types';
import { ManagedSteps, StandaloneSteps, FleetServerSteps } from './steps';
import { DefaultMissingRequirements } from './default_missing_requirements';
export const Instructions = (props: InstructionProps) => {
const {
agentPolicies,
isFleetServerPolicySelected,
settings,
isLoadingAgentPolicies,
setSelectionType,
mode,
isIntegrationFlow,
} = props;
const fleetStatus = useFleetStatus();
const { isUnhealthy: isFleetServerUnhealthy } = useFleetServerUnhealthy();
const { data: agents, isLoading: isLoadingAgents } = useGetAgents({
page: 1,
perPage: 1000,
showInactive: false,
});
const fleetServers = useMemo(() => {
const fleetServerAgentPolicies: string[] = agentPolicies
.filter((pol) => policyHasFleetServer(pol))
.map((pol) => pol.id);
return (agents?.items ?? []).filter((agent) =>
fleetServerAgentPolicies.includes(agent.policy_id ?? '')
);
}, [agents, agentPolicies]);
const fleetServerHosts = useMemo(() => {
return settings?.fleet_server_hosts || [];
}, [settings]);
const hasNoFleetServerHost = fleetStatus.isReady && fleetServerHosts.length === 0;
const showAgentEnrollment =
fleetStatus.isReady &&
!isFleetServerUnhealthy &&
fleetServers.length > 0 &&
fleetServerHosts.length > 0;
const showFleetServerEnrollment =
fleetServers.length === 0 ||
isFleetServerUnhealthy ||
(fleetStatus.missingRequirements ?? []).some((r) => r === FLEET_SERVER_PACKAGE);
if (!isIntegrationFlow && showAgentEnrollment) {
setSelectionType('radio');
} else {
setSelectionType('tabs');
}
if (isLoadingAgents || isLoadingAgentPolicies) return <Loading size="l" />;
if (hasNoFleetServerHost) {
return null;
}
if (mode === 'managed') {
if (showFleetServerEnrollment) {
return <FleetServerRequirementPage />;
} else if (showAgentEnrollment) {
return (
<>
<EuiText>
<FormattedMessage
id="xpack.fleet.agentEnrollment.managedDescription"
defaultMessage="Enroll an Elastic Agent in Fleet to automatically deploy updates and centrally manage the agent."
/>
</EuiText>
<EuiSpacer size="l" />
{isFleetServerPolicySelected ? (
<FleetServerSteps {...props} />
) : (
<ManagedSteps {...props} />
)}
</>
);
}
return <DefaultMissingRequirements />;
} else {
return <StandaloneInstructions {...props} />;
}
};
const StandaloneInstructions = (props: InstructionProps) => {
return (
<>
<EuiText>
<FormattedMessage
id="xpack.fleet.agentEnrollment.standaloneDescription"
defaultMessage="Run an Elastic Agent standalone to configure and update the agent manually on the host where the agent is installed."
/>
</EuiText>
<EuiSpacer size="l" />
<StandaloneSteps {...props} />
</>
);
};

View file

@ -1,206 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useState, useMemo } from 'react';
import { EuiSteps, EuiLink, EuiText, EuiSpacer } from '@elastic/eui';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { useGetOneEnrollmentAPIKey, useLink, useFleetStatus, useGetAgents } from '../../hooks';
import { ManualInstructions } from '../../components/enrollment_instructions';
import {
deploymentModeStep,
ServiceTokenStep,
FleetServerCommandStep,
useFleetServerInstructions,
addFleetServerHostStep,
} from '../../applications/fleet/sections/agents/agent_requirements_page/components';
import { FleetServerRequirementPage } from '../../applications/fleet/sections/agents/agent_requirements_page';
import { policyHasFleetServer } from '../../applications/fleet/sections/agents/services/has_fleet_server';
import { FLEET_SERVER_PACKAGE } from '../../constants';
import { DownloadStep, AgentPolicySelectionStep, AgentEnrollmentKeySelectionStep } from './steps';
import type { InstructionProps } from './types';
const DefaultMissingRequirements = () => {
const { getHref } = useLink();
return (
<>
<FormattedMessage
id="xpack.fleet.agentEnrollment.agentsNotInitializedText"
defaultMessage="Before enrolling agents, {link}."
values={{
link: (
<EuiLink href={getHref('overview')}>
<FormattedMessage
id="xpack.fleet.agentEnrollment.setUpAgentsLink"
defaultMessage="set up central management for Elastic Agents"
/>
</EuiLink>
),
}}
/>
</>
);
};
const FleetServerMissingRequirements = () => {
return <FleetServerRequirementPage />;
};
export const ManagedInstructions = React.memo<InstructionProps>(
({
agentPolicy,
agentPolicies,
viewDataStep,
setSelectedPolicyId,
policyId,
isFleetServerPolicySelected,
isK8s,
settings,
refreshAgentPolicies,
isLoadingAgentPolicies,
}) => {
const fleetStatus = useFleetStatus();
const [selectedApiKeyId, setSelectedAPIKeyId] = useState<string | undefined>();
const apiKey = useGetOneEnrollmentAPIKey(selectedApiKeyId);
const fleetServerInstructions = useFleetServerInstructions(apiKey?.data?.item?.policy_id);
const { data: agents, isLoading: isLoadingAgents } = useGetAgents({
page: 1,
perPage: 1000,
showInactive: false,
});
const fleetServers = useMemo(() => {
const fleetServerAgentPolicies: string[] = agentPolicies
.filter((pol) => policyHasFleetServer(pol))
.map((pol) => pol.id);
return (agents?.items ?? []).filter((agent) =>
fleetServerAgentPolicies.includes(agent.policy_id ?? '')
);
}, [agents, agentPolicies]);
const fleetServerSteps = useMemo(() => {
const {
serviceToken,
getServiceToken,
isLoadingServiceToken,
installCommand,
platform,
setPlatform,
deploymentMode,
setDeploymentMode,
addFleetServerHost,
} = fleetServerInstructions;
return [
deploymentModeStep({ deploymentMode, setDeploymentMode }),
addFleetServerHostStep({ addFleetServerHost }),
ServiceTokenStep({ serviceToken, getServiceToken, isLoadingServiceToken }),
FleetServerCommandStep({ serviceToken, installCommand, platform, setPlatform }),
];
}, [fleetServerInstructions]);
const enrolToken = apiKey.data ? apiKey.data.item.api_key : '';
const steps = useMemo(() => {
const fleetServerHosts = settings?.fleet_server_hosts || [];
const baseSteps: EuiContainedStepProps[] = [
!agentPolicy
? AgentPolicySelectionStep({
agentPolicies,
selectedApiKeyId,
setSelectedAPIKeyId,
setSelectedPolicyId,
refreshAgentPolicies,
})
: AgentEnrollmentKeySelectionStep({ agentPolicy, selectedApiKeyId, setSelectedAPIKeyId }),
DownloadStep(isFleetServerPolicySelected || false, isK8s || '', enrolToken || ''),
];
if (isFleetServerPolicySelected) {
baseSteps.push(...fleetServerSteps);
} else {
baseSteps.push({
title: i18n.translate('xpack.fleet.agentEnrollment.stepEnrollAndRunAgentTitle', {
defaultMessage: 'Enroll and start the Elastic Agent',
}),
children: selectedApiKeyId && apiKey.data && (
<ManualInstructions
apiKey={apiKey.data.item}
fleetServerHosts={fleetServerHosts}
policyId={policyId}
isK8s={isK8s}
/>
),
});
}
if (viewDataStep) {
baseSteps.push({ 'data-test-subj': 'view-data-step', ...viewDataStep });
}
return baseSteps;
}, [
agentPolicy,
selectedApiKeyId,
setSelectedPolicyId,
setSelectedAPIKeyId,
agentPolicies,
refreshAgentPolicies,
apiKey.data,
fleetServerSteps,
isFleetServerPolicySelected,
settings?.fleet_server_hosts,
viewDataStep,
enrolToken,
isK8s,
policyId,
]);
if (fleetStatus.isReady && settings?.fleet_server_hosts.length === 0) {
return null;
}
if (
fleetStatus.isReady &&
(isLoadingAgents || isLoadingAgentPolicies || fleetServers.length > 0)
) {
return (
<>
<EuiText>
<FormattedMessage
id="xpack.fleet.agentEnrollment.managedDescription"
defaultMessage="Enroll an Elastic Agent in Fleet to automatically deploy updates and centrally manage the agent."
/>
</EuiText>
<EuiSpacer size="l" />
<EuiSteps steps={steps} />
</>
);
}
const showFleetMissingRequirements =
fleetServers.length === 0 ||
(fleetStatus.missingRequirements ?? []).some((r) => r === FLEET_SERVER_PACKAGE);
return (
<>
{showFleetMissingRequirements ? (
<FleetServerMissingRequirements />
) : (
<DefaultMissingRequirements />
)}
</>
);
}
);

View file

@ -1,281 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useState, useEffect } from 'react';
import {
EuiSteps,
EuiText,
EuiSpacer,
EuiButton,
EuiCode,
EuiFlexItem,
EuiFlexGroup,
EuiCodeBlock,
EuiCopy,
EuiLink,
} from '@elastic/eui';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { safeDump } from 'js-yaml';
import {
useStartServices,
useLink,
sendGetOneAgentPolicyFull,
useKibanaVersion,
} from '../../hooks';
import { fullAgentPolicyToYaml, agentPolicyRouteService } from '../../services';
import { PlatformSelector } from '../enrollment_instructions/manual/platform_selector';
import { DownloadStep, AgentPolicySelectionStep } from './steps';
import type { InstructionProps } from './types';
import { useIsK8sPolicy, useAgentPolicyWithPackagePolicies } from './hooks';
export const StandaloneInstructions = React.memo<InstructionProps>(
({ agentPolicy, agentPolicies, refreshAgentPolicies }) => {
const { getHref } = useLink();
const core = useStartServices();
const { notifications } = core;
const [selectedPolicyId, setSelectedPolicyId] = useState<string | undefined>(agentPolicy?.id);
const [fullAgentPolicy, setFullAgentPolicy] = useState<any | undefined>();
const [yaml, setYaml] = useState<string | string>('');
const kibanaVersion = useKibanaVersion();
const { agentPolicyWithPackagePolicies } = useAgentPolicyWithPackagePolicies(selectedPolicyId);
const { isK8s } = useIsK8sPolicy(
agentPolicyWithPackagePolicies ? agentPolicyWithPackagePolicies : undefined
);
const KUBERNETES_RUN_INSTRUCTIONS = 'kubectl apply -f elastic-agent-standalone-kubernetes.yaml';
const STANDALONE_RUN_INSTRUCTIONS_LINUX = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-linux-x86_64.tar.gz
tar xzvf elastic-agent-${kibanaVersion}-linux-x86_64.tar.gz
sudo ./elastic-agent install`;
const STANDALONE_RUN_INSTRUCTIONS_MAC = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-darwin-x86_64.tar.gz
tar xzvf elastic-agent-${kibanaVersion}-darwin-x86_64.tar.gz
sudo ./elastic-agent install`;
const STANDALONE_RUN_INSTRUCTIONS_WINDOWS = `wget https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-windows-x86_64.zip -OutFile elastic-agent-${kibanaVersion}-windows-x86_64.zip
Expand-Archive .\\elastic-agent-${kibanaVersion}-windows-x86_64.zip
.\\elastic-agent.exe install`;
const linuxDebCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-amd64.deb
sudo dpkg -i elastic-agent-${kibanaVersion}-amd64.deb \nsudo systemctl enable elastic-agent \nsudo systemctl start elastic-agent`;
const linuxRpmCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-x86_64.rpm
sudo rpm -vi elastic-agent-${kibanaVersion}-x86_64.rpm \nsudo systemctl enable elastic-agent \nsudo systemctl start elastic-agent`;
const linuxCommand =
isK8s === 'IS_KUBERNETES' ? KUBERNETES_RUN_INSTRUCTIONS : STANDALONE_RUN_INSTRUCTIONS_LINUX;
const macCommand =
isK8s === 'IS_KUBERNETES' ? KUBERNETES_RUN_INSTRUCTIONS : STANDALONE_RUN_INSTRUCTIONS_MAC;
const windowsCommand =
isK8s === 'IS_KUBERNETES' ? KUBERNETES_RUN_INSTRUCTIONS : STANDALONE_RUN_INSTRUCTIONS_WINDOWS;
const { docLinks } = useStartServices();
useEffect(() => {
async function fetchFullPolicy() {
try {
if (!selectedPolicyId) {
return;
}
let query = { standalone: true, kubernetes: false };
if (isK8s === 'IS_KUBERNETES') {
query = { standalone: true, kubernetes: true };
}
const res = await sendGetOneAgentPolicyFull(selectedPolicyId, query);
if (res.error) {
throw res.error;
}
if (!res.data) {
throw new Error('No data while fetching full agent policy');
}
setFullAgentPolicy(res.data.item);
} catch (error) {
notifications.toasts.addError(error, {
title: 'Error',
});
}
}
if (isK8s !== 'IS_LOADING') {
fetchFullPolicy();
}
}, [selectedPolicyId, notifications.toasts, isK8s, core.http.basePath]);
useEffect(() => {
if (isK8s === 'IS_KUBERNETES') {
if (typeof fullAgentPolicy === 'object') {
return;
}
setYaml(fullAgentPolicy);
} else {
if (typeof fullAgentPolicy === 'string') {
return;
}
setYaml(fullAgentPolicyToYaml(fullAgentPolicy, safeDump));
}
}, [fullAgentPolicy, isK8s]);
const policyMsg =
isK8s === 'IS_KUBERNETES' ? (
<FormattedMessage
id="xpack.fleet.agentEnrollment.stepConfigureAgentDescriptionk8s"
defaultMessage="Copy or download the Kubernetes manifest inside the Kubernetes cluster. Modify {ESUsernameVariable} and {ESPasswordVariable} in the Daemonset environment variables and apply the manifest."
values={{
ESUsernameVariable: <EuiCode>ES_USERNAME</EuiCode>,
ESPasswordVariable: <EuiCode>ES_PASSWORD</EuiCode>,
}}
/>
) : (
<FormattedMessage
id="xpack.fleet.agentEnrollment.stepConfigureAgentDescription"
defaultMessage="Copy this policy to the {fileName} on the host where the Elastic Agent is installed. Modify {ESUsernameVariable} and {ESPasswordVariable} in the {outputSection} section of {fileName} to use your Elasticsearch credentials."
values={{
fileName: <EuiCode>elastic-agent.yml</EuiCode>,
ESUsernameVariable: <EuiCode>ES_USERNAME</EuiCode>,
ESPasswordVariable: <EuiCode>ES_PASSWORD</EuiCode>,
outputSection: <EuiCode>outputs</EuiCode>,
}}
/>
);
let downloadLink = '';
if (selectedPolicyId) {
downloadLink =
isK8s === 'IS_KUBERNETES'
? core.http.basePath.prepend(
`${agentPolicyRouteService.getInfoFullDownloadPath(
selectedPolicyId
)}?kubernetes=true&standalone=true`
)
: core.http.basePath.prepend(
`${agentPolicyRouteService.getInfoFullDownloadPath(selectedPolicyId)}?standalone=true`
);
}
const downloadMsg =
isK8s === 'IS_KUBERNETES' ? (
<FormattedMessage
id="xpack.fleet.agentEnrollment.downloadPolicyButtonk8s"
defaultMessage="Download Manifest"
/>
) : (
<FormattedMessage
id="xpack.fleet.agentEnrollment.downloadPolicyButton"
defaultMessage="Download Policy"
/>
);
const steps = [
!agentPolicy
? AgentPolicySelectionStep({
agentPolicies,
setSelectedPolicyId,
excludeFleetServer: true,
refreshAgentPolicies,
})
: undefined,
DownloadStep(false),
{
title: i18n.translate('xpack.fleet.agentEnrollment.stepConfigureAgentTitle', {
defaultMessage: 'Configure the agent',
}),
children: (
<>
<EuiText>
<>{policyMsg}</>
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="m">
<EuiFlexItem grow={false}>
<EuiCopy textToCopy={yaml}>
{(copy) => (
<EuiButton onClick={copy} iconType="copyClipboard">
<FormattedMessage
id="xpack.fleet.agentEnrollment.copyPolicyButton"
defaultMessage="Copy to clipboard"
/>
</EuiButton>
)}
</EuiCopy>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton iconType="download" href={downloadLink} isDisabled={!downloadLink}>
<>{downloadMsg}</>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
<EuiCodeBlock language="yaml" style={{ maxHeight: 300 }} fontSize="m">
{yaml}
</EuiCodeBlock>
</EuiText>
</>
),
},
{
title: i18n.translate('xpack.fleet.agentEnrollment.stepRunAgentTitle', {
defaultMessage: 'Start the agent',
}),
children: (
<PlatformSelector
linuxCommand={linuxCommand}
macCommand={macCommand}
windowsCommand={windowsCommand}
linuxDebCommand={linuxDebCommand}
linuxRpmCommand={linuxRpmCommand}
troubleshootLink={docLinks.links.fleet.troubleshooting}
isK8s={isK8s === 'IS_KUBERNETES'}
/>
),
},
{
title: i18n.translate('xpack.fleet.agentEnrollment.stepCheckForDataTitle', {
defaultMessage: 'Check for data',
}),
children: (
<>
<EuiText>
<FormattedMessage
id="xpack.fleet.agentEnrollment.stepCheckForDataDescription"
defaultMessage="The agent should begin sending data. Go to {link} to view your data."
values={{
link: (
<EuiLink href={getHref('data_streams')}>
<FormattedMessage
id="xpack.fleet.agentEnrollment.goToDataStreamsLink"
defaultMessage="data streams"
/>
</EuiLink>
),
}}
/>
</EuiText>
</>
),
},
].filter(Boolean) as EuiContainedStepProps[];
return (
<>
<EuiText>
<FormattedMessage
id="xpack.fleet.agentEnrollment.standaloneDescription"
defaultMessage="Run an Elastic Agent standalone to configure and update the agent manually on the host where the agent is installed."
/>
</EuiText>
<EuiSpacer size="l" />
<EuiSteps steps={steps} />
</>
);
}
);

View file

@ -0,0 +1,41 @@
/*
* 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 { i18n } from '@kbn/i18n';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import { ConfirmAgentEnrollment } from '../confirm_agent_enrollment';
export const AgentEnrollmentConfirmationStep = ({
selectedPolicyId,
troubleshootLink,
onClickViewAgents,
agentCount,
}: {
selectedPolicyId?: string;
troubleshootLink: string;
onClickViewAgents: () => void;
agentCount: number;
}): EuiContainedStepProps => {
return {
title: i18n.translate('xpack.fleet.agentEnrollment.stepAgentEnrollmentConfirmation', {
defaultMessage: 'Confirm agent Enrollment',
}),
children: (
<ConfirmAgentEnrollment
policyId={selectedPolicyId}
troubleshootLink={troubleshootLink}
onClickViewAgents={onClickViewAgents}
agentCount={agentCount}
/>
),
status: !agentCount ? 'incomplete' : 'complete',
};
};

View file

@ -0,0 +1,53 @@
/*
* 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 { EuiText, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import type { AgentPolicy } from '../../../types';
import { AdvancedAgentAuthenticationSettings } from '../advanced_agent_authentication_settings';
export const AgentEnrollmentKeySelectionStep = ({
selectedPolicy,
selectedApiKeyId,
setSelectedAPIKeyId,
}: {
selectedPolicy?: AgentPolicy;
selectedApiKeyId?: string;
setSelectedAPIKeyId: (key?: string) => void;
}): EuiContainedStepProps => {
return {
title: i18n.translate('xpack.fleet.agentEnrollment.stepConfigurePolicyAuthenticationTitle', {
defaultMessage: 'Select enrollment token',
}),
children: (
<>
<EuiText>
<FormattedMessage
id="xpack.fleet.agentEnrollment.agentAuthenticationSettings"
defaultMessage="{agentPolicyName} has been selected. Select which enrollment token to use when enrolling agents."
values={{
agentPolicyName: <strong>{selectedPolicy?.name}</strong>,
}}
/>
</EuiText>
<EuiSpacer size="l" />
<AdvancedAgentAuthenticationSettings
agentPolicyId={selectedPolicy?.id}
selectedApiKeyId={selectedApiKeyId}
initialAuthenticationSettingsOpen
onKeyChange={setSelectedAPIKeyId}
/>
</>
),
};
};

View file

@ -0,0 +1,54 @@
/*
* 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 { i18n } from '@kbn/i18n';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import type { AgentPolicy } from '../../../types';
import { SelectCreateAgentPolicy } from '../agent_policy_select_create';
export const AgentPolicySelectionStep = ({
agentPolicies,
selectedPolicy,
setSelectedPolicyId,
selectedApiKeyId,
setSelectedAPIKeyId,
excludeFleetServer,
refreshAgentPolicies,
}: {
agentPolicies: AgentPolicy[];
selectedPolicy?: AgentPolicy;
setSelectedPolicyId: (agentPolicyId?: string) => void;
selectedApiKeyId?: string;
setSelectedAPIKeyId?: (key?: string) => void;
excludeFleetServer?: boolean;
refreshAgentPolicies: () => void;
}): EuiContainedStepProps => {
return {
title: i18n.translate('xpack.fleet.agentEnrollment.stepChooseAgentPolicyTitle', {
defaultMessage: 'What type of host are you adding?',
}),
children: (
<>
<SelectCreateAgentPolicy
agentPolicies={agentPolicies}
selectedPolicy={selectedPolicy}
setSelectedPolicyId={setSelectedPolicyId}
withKeySelection={setSelectedAPIKeyId ? true : false}
selectedApiKeyId={selectedApiKeyId}
onKeyChange={setSelectedAPIKeyId}
refreshAgentPolicies={refreshAgentPolicies}
excludeFleetServer={excludeFleetServer}
/>
</>
),
};
};

View file

@ -0,0 +1,369 @@
/*
* 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, { useState, useMemo, useEffect } from 'react';
import { EuiSteps } from '@elastic/eui';
import { safeDump } from 'js-yaml';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import type { FullAgentPolicy } from '../../../../common/types/models/agent_policy';
import { fullAgentPolicyToYaml, agentPolicyRouteService } from '../../../services';
import { StandaloneInstructions } from '../../enrollment_instructions/standalone';
import {
useGetOneEnrollmentAPIKey,
useStartServices,
useKibanaVersion,
sendGetOneAgentPolicyFull,
} from '../../../hooks';
import {
deploymentModeStep,
ServiceTokenStep,
FleetServerCommandStep,
useFleetServerInstructions,
addFleetServerHostStep,
} from '../../../applications/fleet/sections/agents/agent_requirements_page/components';
import type { InstructionProps } from '../types';
import { usePollingAgentCount } from '../confirm_agent_enrollment';
import {
InstallationModeSelectionStep,
AgentEnrollmentKeySelectionStep,
AgentPolicySelectionStep,
InstallStandaloneAgentStep,
ConfigureStandaloneAgentStep,
AgentEnrollmentConfirmationStep,
InstallManagedAgentStep,
IncomingDataConfirmationStep,
} from '.';
export const StandaloneSteps: React.FunctionComponent<InstructionProps> = ({
agentPolicy,
agentPolicies,
selectedPolicy,
setSelectedPolicyId,
refreshAgentPolicies,
mode,
setMode,
selectionType,
selectedApiKeyId,
setSelectedAPIKeyId,
isK8s,
}) => {
const core = useStartServices();
const { notifications } = core;
const [fullAgentPolicy, setFullAgentPolicy] = useState<FullAgentPolicy | undefined>();
const [yaml, setYaml] = useState<any | undefined>('');
const kibanaVersion = useKibanaVersion();
let downloadLink = '';
if (selectedPolicy?.id) {
downloadLink =
isK8s === 'IS_KUBERNETES'
? core.http.basePath.prepend(
`${agentPolicyRouteService.getInfoFullDownloadPath(
selectedPolicy?.id
)}?kubernetes=true&standalone=true`
)
: core.http.basePath.prepend(
`${agentPolicyRouteService.getInfoFullDownloadPath(selectedPolicy?.id)}?standalone=true`
);
}
useEffect(() => {
async function fetchFullPolicy() {
try {
if (!selectedPolicy?.id) {
return;
}
let query = { standalone: true, kubernetes: false };
if (isK8s === 'IS_KUBERNETES') {
query = { standalone: true, kubernetes: true };
}
const res = await sendGetOneAgentPolicyFull(selectedPolicy?.id, query);
if (res.error) {
throw res.error;
}
if (!res.data) {
throw new Error('No data while fetching full agent policy');
}
setFullAgentPolicy(res.data.item);
} catch (error) {
notifications.toasts.addError(error, {
title: 'Error',
});
}
}
if (isK8s !== 'IS_LOADING') {
fetchFullPolicy();
}
}, [selectedPolicy, notifications.toasts, isK8s, core.http.basePath]);
useEffect(() => {
if (!fullAgentPolicy) {
return;
}
if (isK8s === 'IS_KUBERNETES') {
if (typeof fullAgentPolicy === 'object') {
return;
}
setYaml(fullAgentPolicy);
} else {
if (typeof fullAgentPolicy === 'string') {
return;
}
setYaml(fullAgentPolicyToYaml(fullAgentPolicy, safeDump));
}
}, [fullAgentPolicy, isK8s]);
const instructionsSteps = useMemo(() => {
const standaloneInstallCommands = StandaloneInstructions(kibanaVersion, isK8s);
const steps: EuiContainedStepProps[] = !agentPolicy
? [
AgentPolicySelectionStep({
selectedPolicy,
agentPolicies,
selectedApiKeyId,
setSelectedAPIKeyId,
setSelectedPolicyId,
refreshAgentPolicies,
}),
]
: [];
if (selectionType === 'radio') {
steps.push(InstallationModeSelectionStep({ mode, setMode }));
}
steps.push(
ConfigureStandaloneAgentStep({
isK8s,
selectedPolicyId: selectedPolicy?.id,
yaml,
downloadLink,
})
);
steps.push(
InstallStandaloneAgentStep({
installCommand: standaloneInstallCommands,
isK8s,
selectedPolicyId: selectedPolicy?.id,
})
);
return steps;
}, [
kibanaVersion,
isK8s,
agentPolicy,
selectedPolicy,
agentPolicies,
selectedApiKeyId,
setSelectedAPIKeyId,
setSelectedPolicyId,
refreshAgentPolicies,
selectionType,
yaml,
downloadLink,
mode,
setMode,
]);
return <EuiSteps steps={instructionsSteps} />;
};
export const ManagedSteps: React.FunctionComponent<InstructionProps> = ({
agentPolicy,
agentPolicies,
selectedPolicy,
setSelectedPolicyId,
selectedApiKeyId,
setSelectedAPIKeyId,
settings,
refreshAgentPolicies,
mode,
setMode,
selectionType,
onClickViewAgents,
isK8s,
installedPackagePolicy,
}) => {
const core = useStartServices();
const { docLinks } = core;
const link = docLinks.links.fleet.troubleshooting;
const [agentDataConfirmed, setAgentDataConfirmed] = useState<boolean>(false);
const apiKey = useGetOneEnrollmentAPIKey(selectedApiKeyId);
const apiKeyData = apiKey?.data;
const enrolledAgentIds = usePollingAgentCount(selectedPolicy?.id || '');
const fleetServerHosts = useMemo(() => {
return settings?.fleet_server_hosts || [];
}, [settings]);
const instructionsSteps = useMemo(() => {
const steps: EuiContainedStepProps[] = !agentPolicy
? [
AgentPolicySelectionStep({
selectedPolicy,
agentPolicies,
selectedApiKeyId,
setSelectedAPIKeyId,
setSelectedPolicyId,
refreshAgentPolicies,
}),
]
: [
AgentEnrollmentKeySelectionStep({
selectedPolicy,
selectedApiKeyId,
setSelectedAPIKeyId,
}),
];
if (selectionType === 'radio') {
steps.push(InstallationModeSelectionStep({ mode, setMode }));
}
steps.push(
InstallManagedAgentStep({
apiKeyData,
selectedApiKeyId,
fleetServerHosts,
isK8s,
})
);
if (selectedApiKeyId && apiKeyData) {
steps.push(
AgentEnrollmentConfirmationStep({
selectedPolicyId: selectedPolicy?.id,
onClickViewAgents,
troubleshootLink: link,
agentCount: enrolledAgentIds.length,
})
);
}
if (selectedPolicy && enrolledAgentIds.length) {
steps.push(
IncomingDataConfirmationStep({
agentIds: enrolledAgentIds,
agentDataConfirmed,
setAgentDataConfirmed,
installedPolicy: installedPackagePolicy,
})
);
}
return steps;
}, [
agentPolicy,
selectedPolicy,
agentPolicies,
selectedApiKeyId,
setSelectedAPIKeyId,
setSelectedPolicyId,
refreshAgentPolicies,
selectionType,
apiKeyData,
fleetServerHosts,
isK8s,
mode,
setMode,
onClickViewAgents,
link,
enrolledAgentIds,
agentDataConfirmed,
installedPackagePolicy,
]);
return <EuiSteps steps={instructionsSteps} />;
};
export const FleetServerSteps: React.FunctionComponent<InstructionProps> = ({
agentPolicy,
agentPolicies,
selectedPolicy,
setSelectedPolicyId,
refreshAgentPolicies,
}) => {
const [selectedApiKeyId, setSelectedAPIKeyId] = useState<string | undefined>();
const apiKey = useGetOneEnrollmentAPIKey(selectedApiKeyId);
const apiKeyData = apiKey?.data;
const fleetServerInstructions = useFleetServerInstructions(apiKeyData?.item?.policy_id);
const fleetServerSteps = useMemo(() => {
const {
serviceToken,
getServiceToken,
isLoadingServiceToken,
installCommand: managedInstallCommands,
platform,
setPlatform,
deploymentMode,
setDeploymentMode,
addFleetServerHost,
} = fleetServerInstructions;
return [
deploymentModeStep({ deploymentMode, setDeploymentMode }),
addFleetServerHostStep({ addFleetServerHost }),
ServiceTokenStep({ serviceToken, getServiceToken, isLoadingServiceToken }),
FleetServerCommandStep({
serviceToken,
installCommand: managedInstallCommands,
platform,
setPlatform,
}),
];
}, [fleetServerInstructions]);
const instructionsSteps = useMemo(() => {
const steps: EuiContainedStepProps[] = !agentPolicy
? [
AgentPolicySelectionStep({
selectedPolicy,
agentPolicies,
selectedApiKeyId,
setSelectedAPIKeyId,
setSelectedPolicyId,
refreshAgentPolicies,
}),
]
: [
AgentEnrollmentKeySelectionStep({
selectedPolicy,
selectedApiKeyId,
setSelectedAPIKeyId,
}),
];
steps.push(...fleetServerSteps);
return steps;
}, [
agentPolicy,
selectedPolicy,
agentPolicies,
selectedApiKeyId,
setSelectedPolicyId,
refreshAgentPolicies,
fleetServerSteps,
]);
return <EuiSteps steps={instructionsSteps} />;
};

View file

@ -0,0 +1,116 @@
/*
* 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 {
EuiText,
EuiButton,
EuiSpacer,
EuiCode,
EuiFlexGroup,
EuiFlexItem,
EuiCopy,
EuiCodeBlock,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import type { K8sMode } from '../types';
export const ConfigureStandaloneAgentStep = ({
isK8s,
selectedPolicyId,
yaml,
downloadLink,
}: {
isK8s?: K8sMode;
selectedPolicyId?: string;
yaml: string;
downloadLink: string;
}): EuiContainedStepProps => {
const policyMsg =
isK8s === 'IS_KUBERNETES' ? (
<FormattedMessage
id="xpack.fleet.agentEnrollment.stepConfigureAgentDescriptionk8s"
defaultMessage="Copy or download the Kubernetes manifest inside the Kubernetes cluster. Modify {ESUsernameVariable} and {ESPasswordVariable} in the Daemonset environment variables and apply the manifest."
values={{
ESUsernameVariable: <EuiCode>ES_USERNAME</EuiCode>,
ESPasswordVariable: <EuiCode>ES_PASSWORD</EuiCode>,
}}
/>
) : (
<FormattedMessage
id="xpack.fleet.agentEnrollment.stepConfigureAgentDescription"
defaultMessage="Copy this policy to the {fileName} on the host where the Elastic Agent is installed. Modify {ESUsernameVariable} and {ESPasswordVariable} in the {outputSection} section of {fileName} to use your Elasticsearch credentials."
values={{
fileName: <EuiCode>elastic-agent.yml</EuiCode>,
ESUsernameVariable: <EuiCode>ES_USERNAME</EuiCode>,
ESPasswordVariable: <EuiCode>ES_PASSWORD</EuiCode>,
outputSection: <EuiCode>outputs</EuiCode>,
}}
/>
);
const downloadMsg =
isK8s === 'IS_KUBERNETES' ? (
<FormattedMessage
id="xpack.fleet.agentEnrollment.downloadPolicyButtonk8s"
defaultMessage="Download Manifest"
/>
) : (
<FormattedMessage
id="xpack.fleet.agentEnrollment.downloadPolicyButton"
defaultMessage="Download Policy"
/>
);
return {
title: i18n.translate('xpack.fleet.agentEnrollment.stepConfigureAgentTitle', {
defaultMessage: 'Configure the agent',
}),
children: (
<>
{!yaml ? null : (
<EuiText>
<>{policyMsg}</>
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="m">
<EuiFlexItem grow={false}>
<EuiCopy textToCopy={yaml}>
{(copy) => (
<EuiButton onClick={copy} iconType="copyClipboard">
<FormattedMessage
id="xpack.fleet.agentEnrollment.copyPolicyButton"
defaultMessage="Copy to clipboard"
/>
</EuiButton>
)}
</EuiCopy>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton iconType="download" href={downloadLink} isDisabled={!downloadLink}>
<>{downloadMsg}</>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
<EuiCodeBlock
language="yaml"
style={{ maxHeight: 300 }}
fontSize="m"
data-test-subj="agentPolicyCodeBlock"
>
{yaml}
</EuiCodeBlock>
</EuiText>
)}
</>
),
status: !yaml ? 'loading' : 'incomplete',
};
};

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import {
EuiText,
EuiButton,
@ -22,21 +22,19 @@ import semverMajor from 'semver/functions/major';
import semverMinor from 'semver/functions/minor';
import semverPatch from 'semver/functions/patch';
import type { AgentPolicy } from '../../types';
import { useGetSettings, useKibanaVersion, useStartServices } from '../../hooks';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import { agentPolicyRouteService } from '../../../common';
import { useGetSettings, useKibanaVersion, useStartServices } from '../../../hooks';
import { sendGetK8sManifest } from '../../hooks/use_request/k8s';
import { agentPolicyRouteService } from '../../../../common';
import { AdvancedAgentAuthenticationSettings } from './advanced_agent_authentication_settings';
import { SelectCreateAgentPolicy } from './agent_policy_select_create';
import { sendGetK8sManifest } from '../../../hooks/use_request/k8s';
export const DownloadStep = (
hasFleetServer: boolean,
isK8s?: string,
enrollmentAPIKey?: string
) => {
): EuiContainedStepProps => {
const kibanaVersion = useKibanaVersion();
const core = useStartServices();
const settings = useGetSettings();
@ -49,6 +47,7 @@ export const DownloadStep = (
const [yaml, setYaml] = useState<any | undefined>();
const [fleetServer, setFleetServer] = useState<string | ''>();
useEffect(() => {
async function fetchK8sManifest() {
try {
@ -132,6 +131,7 @@ export const DownloadStep = (
) : (
''
);
const k8sCopyYaml =
isK8s === 'IS_KUBERNETES' ? (
<EuiCopy textToCopy={yaml}>
@ -147,6 +147,7 @@ export const DownloadStep = (
) : (
''
);
const k8sYaml =
isK8s === 'IS_KUBERNETES' ? (
<EuiCodeBlock language="yaml" style={{ maxHeight: 300 }} fontSize="m">
@ -202,96 +203,3 @@ export const DownloadStep = (
),
};
};
export const AgentPolicySelectionStep = ({
agentPolicies,
setSelectedPolicyId,
selectedApiKeyId,
setSelectedAPIKeyId,
excludeFleetServer,
refreshAgentPolicies,
}: {
agentPolicies: AgentPolicy[];
setSelectedPolicyId?: (policyId?: string) => void;
selectedApiKeyId?: string;
setSelectedAPIKeyId?: (key?: string) => void;
excludeFleetServer?: boolean;
refreshAgentPolicies: () => void;
}) => {
// storing the created agent policy id as the child component is being recreated
const [policyId, setPolicyId] = useState<string | undefined>(undefined);
const regularAgentPolicies = useMemo(() => {
return agentPolicies.filter(
(policy) =>
policy && !policy.is_managed && (!excludeFleetServer || !policy.is_default_fleet_server)
);
}, [agentPolicies, excludeFleetServer]);
const onAgentPolicyChange = useCallback(
async (key?: string, policy?: AgentPolicy) => {
if (policy) {
refreshAgentPolicies();
}
if (setSelectedPolicyId) {
setSelectedPolicyId(key);
setPolicyId(key);
}
},
[setSelectedPolicyId, refreshAgentPolicies]
);
return {
title: i18n.translate('xpack.fleet.agentEnrollment.stepChooseAgentPolicyTitle', {
defaultMessage: 'What type of host are you adding?',
}),
children: (
<>
<SelectCreateAgentPolicy
agentPolicies={regularAgentPolicies}
withKeySelection={setSelectedAPIKeyId ? true : false}
selectedApiKeyId={selectedApiKeyId}
onKeyChange={setSelectedAPIKeyId}
onAgentPolicyChange={onAgentPolicyChange}
excludeFleetServer={excludeFleetServer}
policyId={policyId}
/>
</>
),
};
};
export const AgentEnrollmentKeySelectionStep = ({
agentPolicy,
selectedApiKeyId,
setSelectedAPIKeyId,
}: {
agentPolicy: AgentPolicy;
selectedApiKeyId?: string;
setSelectedAPIKeyId: (key?: string) => void;
}) => {
return {
title: i18n.translate('xpack.fleet.agentEnrollment.stepConfigurePolicyAuthenticationTitle', {
defaultMessage: 'Select enrollment token',
}),
children: (
<>
<EuiText>
<FormattedMessage
id="xpack.fleet.agentEnrollment.agentAuthenticationSettings"
defaultMessage="{agentPolicyName} has been selected. Select which enrollment token to use when enrolling agents."
values={{
agentPolicyName: <strong>{agentPolicy.name}</strong>,
}}
/>
</EuiText>
<EuiSpacer size="l" />
<AdvancedAgentAuthenticationSettings
agentPolicyId={agentPolicy.id}
selectedApiKeyId={selectedApiKeyId}
initialAuthenticationSettingsOpen
onKeyChange={setSelectedAPIKeyId}
/>
</>
),
};
};

View file

@ -0,0 +1,47 @@
/*
* 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 { i18n } from '@kbn/i18n';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import type { InstalledIntegrationPolicy } from '../use_get_agent_incoming_data';
import { ConfirmIncomingData } from '../confirm_incoming_data';
export const IncomingDataConfirmationStep = ({
agentIds,
installedPolicy,
agentDataConfirmed,
setAgentDataConfirmed,
}: {
agentIds: string[];
installedPolicy?: InstalledIntegrationPolicy;
agentDataConfirmed: boolean;
setAgentDataConfirmed: (v: boolean) => void;
}): EuiContainedStepProps => {
return {
title: !agentDataConfirmed
? i18n.translate('xpack.fleet.agentEnrollment.stepConfirmIncomingData', {
defaultMessage: 'Confirm incoming data',
})
: i18n.translate('xpack.fleet.agentEnrollment.stepConfirmIncomingData.completed', {
defaultMessage: 'Incoming data confirmed',
}),
children: (
<ConfirmIncomingData
agentIds={agentIds}
installedPolicy={installedPolicy}
agentDataConfirmed={agentDataConfirmed}
setAgentDataConfirmed={setAgentDataConfirmed}
/>
),
status: !agentDataConfirmed ? 'loading' : 'complete',
};
};

View file

@ -0,0 +1,17 @@
/*
* 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 * from './agent_enrollment_confirmation_step';
export * from './agent_enrollment_key_selection_step';
export * from './agent_policy_selection_step';
export * from './configure_standalone_agent_step';
export * from './download_step';
export * from './incoming_data_confirmation_step';
export * from './install_managed_agent_step';
export * from './install_standalone_agent_step';
export * from './installation_mode_selection_step';
export * from './compute_steps';

View file

@ -0,0 +1,41 @@
/*
* 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 { i18n } from '@kbn/i18n';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import type { GetOneEnrollmentAPIKeyResponse } from '../../../../common/types/rest_spec/enrollment_api_key';
import { ManualInstructions } from '../../enrollment_instructions';
export const InstallManagedAgentStep = ({
selectedApiKeyId,
apiKeyData,
fleetServerHosts,
isK8s,
}: {
fleetServerHosts: string[];
selectedApiKeyId?: string;
apiKeyData?: GetOneEnrollmentAPIKeyResponse | null;
isK8s?: string;
}): EuiContainedStepProps => {
return {
title: i18n.translate('xpack.fleet.agentEnrollment.stepEnrollAndRunAgentTitle', {
defaultMessage: 'Install Elastic Agent on your host',
}),
children: selectedApiKeyId && apiKeyData && (
<ManualInstructions
apiKey={apiKeyData.item}
fleetServerHosts={fleetServerHosts}
isK8s={isK8s}
/>
),
};
};

View file

@ -0,0 +1,47 @@
/*
* 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 { i18n } from '@kbn/i18n';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import type { CommandsByPlatform } from '../../../applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils';
import { PlatformSelector } from '../../enrollment_instructions/manual/platform_selector';
import { InstallationMessage } from '../installation_message';
import type { K8sMode } from '../types';
export const InstallStandaloneAgentStep = ({
installCommand,
isK8s,
selectedPolicyId,
}: {
installCommand: CommandsByPlatform;
isK8s?: K8sMode;
selectedPolicyId?: string;
}): EuiContainedStepProps => {
return {
title: i18n.translate('xpack.fleet.agentEnrollment.stepEnrollAndRunAgentTitle', {
defaultMessage: 'Install Elastic Agent on your host',
}),
children: (
<>
<InstallationMessage />
<PlatformSelector
linuxCommand={installCommand.linux}
macCommand={installCommand.mac}
windowsCommand={installCommand.windows}
linuxDebCommand={installCommand.deb}
linuxRpmCommand={installCommand.rpm}
isK8s={isK8s === 'IS_KUBERNETES'}
/>
</>
),
};
};

View file

@ -0,0 +1,88 @@
/*
* 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 { EuiRadioGroup } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import type { FlyoutMode } from '../types';
export const InstallationModeSelectionStep = ({
mode,
setMode,
}: {
mode: FlyoutMode;
setMode: (v: FlyoutMode) => void;
}): EuiContainedStepProps => {
// radio id has to be unique so that the component works even if appears twice in DOM
const radioSuffix = 'installation_mode_agent_selection';
const onChangeCallback = (v: string) => {
const value = v.split('_')[0];
if (value === 'managed' || value === 'standalone') {
setMode(value);
}
};
return {
title: i18n.translate('xpack.fleet.agentEnrollment.stepInstallType', {
defaultMessage: 'Enroll in Fleet?',
}),
children: (
<EuiRadioGroup
options={[
{
id: `managed_${radioSuffix}`,
label: (
<FormattedMessage
data-test-subj="agentFlyoutManagedRadioButtons"
id="xpack.fleet.agentFlyout.managedRadioOption"
defaultMessage="{managed} Enroll in Elastic Agent in Fleet to automatically deploy updates and centrally manage the agent."
values={{
managed: (
<strong>
<FormattedMessage
id="xpack.fleet.agentFlyout.managedMessage"
defaultMessage="Enroll in Fleet (recommended)"
/>
</strong>
),
}}
/>
),
},
{
id: `standalone_${radioSuffix}`,
label: (
<FormattedMessage
data-test-subj="agentFlyoutStandaloneRadioButtons"
id="xpack.fleet.agentFlyout.standaloneRadioOption"
defaultMessage="{standaloneMessage} Run an Elastic Agent standalone to configure and update the agent manually on the host where the agent is installed."
values={{
standaloneMessage: (
<strong>
<FormattedMessage
id="xpack.fleet.agentFlyout.standaloneMessage"
defaultMessage="Run standalone"
/>
</strong>
),
}}
/>
),
},
]}
idSelected={`${mode}_${radioSuffix}`}
onChange={onChangeCallback}
name={`radio group ${radioSuffix}`}
/>
),
};
};

View file

@ -5,35 +5,50 @@
* 2.0.
*/
import type { EuiStepProps } from '@elastic/eui';
import type { AgentPolicy, Settings } from '../../types';
import type { InstalledIntegrationPolicy } from './use_get_agent_incoming_data';
export type K8sMode = 'IS_LOADING' | 'IS_KUBERNETES' | 'IS_NOT_KUBERNETES';
export type FlyoutMode = 'managed' | 'standalone';
export type SelectionType = 'tabs' | 'radio';
export interface BaseProps {
/**
* The user selected policy to be used. If this value is `undefined` a value must be provided for `agentPolicies`.
*/
agentPolicy?: AgentPolicy;
settings?: Settings;
isFleetServerPolicySelected?: boolean;
isK8s?: K8sMode;
/**
* There is a step in the agent enrollment process that allows users to see the data from an integration represented in the UI
* in some way. This is an area for consumers to render a button and text explaining how data can be viewed.
*/
viewDataStep?: EuiStepProps;
isIntegrationFlow?: boolean;
installedPackagePolicy?: InstalledIntegrationPolicy;
}
settings?: Settings;
setSelectedPolicyId?: (policyId?: string) => void;
policyId?: string;
isFleetServerPolicySelected?: boolean;
isK8s?: string;
export interface FlyOutProps extends BaseProps {
onClose: () => void;
defaultMode?: FlyoutMode;
}
export interface InstructionProps extends BaseProps {
agentPolicies: AgentPolicy[];
selectedPolicy: AgentPolicy | undefined;
setSelectedPolicyId: (policyId?: string) => void;
refreshAgentPolicies: () => void;
isLoadingAgentPolicies?: boolean;
onClickViewAgents: () => void;
mode: FlyoutMode;
setMode: (v: FlyoutMode) => void;
selectionType: SelectionType;
setSelectionType: (type: SelectionType) => void;
selectedApiKeyId?: string;
setSelectedAPIKeyId: (key?: string) => void;
}

View file

@ -0,0 +1,121 @@
/*
* 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 { useEffect, useState, useMemo, useRef } from 'react';
import { i18n } from '@kbn/i18n';
import type { IncomingDataList } from '../../../common/types/rest_spec/agent';
import { sendGetAgentIncomingData, useLink } from '../../hooks/index';
export interface InstalledIntegrationPolicy {
name: string;
version: string;
}
export const useGetAgentIncomingData = (
incomingData: IncomingDataList[],
installedPolicy?: InstalledIntegrationPolicy
) => {
const enrolledAgents = useMemo(() => incomingData.length, [incomingData.length]);
const numAgentsWithData = useMemo(
() =>
incomingData.reduce((acc, curr) => {
const agentData = Object.values(curr)[0];
return !!agentData.data ? acc + 1 : acc;
}, 0),
[incomingData]
);
const { getAbsolutePath, getHref } = useLink();
let href;
let text;
let message;
if (!installedPolicy) {
href = '';
text = '';
message = '';
}
if (installedPolicy?.name === 'apm') {
href = getAbsolutePath('/app/home#/tutorial/apm');
text = i18n.translate('xpack.fleet.confirmIncomingData.installApmAgentButtonText', {
defaultMessage: 'Install APM Agent',
});
message = i18n.translate('xpack.fleet.confirmIncomingData.APMsubtitle', {
defaultMessage:
'Next, install APM agents on your hosts to collect data from your applications and services.',
});
} else {
href = getHref('integration_details_assets', {
pkgkey: `${installedPolicy?.name}-${installedPolicy?.version}`,
});
text = i18n.translate('xpack.fleet.confirmIncomingData.viewDataAssetsButtonText', {
defaultMessage: 'View assets',
});
message = i18n.translate('xpack.fleet.confirmIncomingData.subtitle', {
defaultMessage:
'Next, analyze your data using our integration assets such as curated views, dashboards and more.',
});
}
const linkButton = { href, text };
return {
enrolledAgents,
numAgentsWithData,
linkButton,
message,
};
};
/**
* Hook for polling incoming data for the selected agent policy.
* @param agentIds
* @returns incomingData, isLoading
*/
const POLLING_INTERVAL_MS = 5 * 1000; // 5 sec
export const usePollingIncomingData = (agentsIds: string[]) => {
const timeout = useRef<number | undefined>(undefined);
const [incomingData, setIncomingData] = useState<IncomingDataList[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
useEffect(() => {
let isAborted = false;
const poll = () => {
timeout.current = window.setTimeout(async () => {
const { data } = await sendGetAgentIncomingData({ agentsIds });
if (data?.items) {
// filter out agents that have `data = false` and keep polling
const filtered = data?.items.filter((item) => {
const key = Object.keys(item)[0];
return item[key].data === true;
});
if (filtered.length > 0) {
setIncomingData(filtered);
setIsLoading(false);
}
}
if (!isAborted) {
poll();
}
}, POLLING_INTERVAL_MS);
};
poll();
if (isAborted || incomingData.length > 0) clearTimeout(timeout.current);
return () => {
isAborted = true;
};
}, [agentsIds, incomingData]);
return { incomingData, isLoading };
};

View file

@ -7,15 +7,16 @@
import React from 'react';
import { useStartServices, useKibanaVersion } from '../../../hooks';
import { useKibanaVersion } from '../../../hooks';
import type { EnrollmentAPIKey } from '../../../types';
import { InstallationMessage } from '../../agent_enrollment_flyout/installation_message';
import { PlatformSelector } from './platform_selector';
interface Props {
fleetServerHosts: string[];
apiKey: EnrollmentAPIKey;
policyId: string | undefined;
isK8s: string | undefined;
}
@ -26,10 +27,8 @@ function getfleetServerHostsEnrollArgs(apiKey: EnrollmentAPIKey, fleetServerHost
export const ManualInstructions: React.FunctionComponent<Props> = ({
apiKey,
fleetServerHosts,
policyId,
isK8s,
}) => {
const { docLinks } = useStartServices();
const enrollArgs = getfleetServerHostsEnrollArgs(apiKey, fleetServerHosts);
const kibanaVersion = useKibanaVersion();
@ -57,14 +56,16 @@ sudo rpm -vi elastic-agent-${kibanaVersion}-x86_64.rpm
sudo elastic-agent enroll ${enrollArgs} \nsudo systemctl enable elastic-agent \nsudo systemctl start elastic-agent`;
return (
<PlatformSelector
linuxCommand={linuxCommand}
macCommand={macCommand}
windowsCommand={windowsCommand}
linuxDebCommand={linuxDebCommand}
linuxRpmCommand={linuxRpmCommand}
troubleshootLink={docLinks.links.fleet.troubleshooting}
isK8s={isK8s === 'IS_KUBERNETES'}
/>
<>
<InstallationMessage />
<PlatformSelector
linuxCommand={linuxCommand}
macCommand={macCommand}
windowsCommand={windowsCommand}
linuxDebCommand={linuxDebCommand}
linuxRpmCommand={linuxRpmCommand}
isK8s={isK8s === 'IS_KUBERNETES'}
/>
</>
);
};

View file

@ -7,14 +7,7 @@
import React from 'react';
import styled from 'styled-components';
import {
EuiText,
EuiSpacer,
EuiLink,
EuiCodeBlock,
EuiButtonGroup,
EuiCallOut,
} from '@elastic/eui';
import { EuiText, EuiSpacer, EuiCodeBlock, EuiButtonGroup, EuiCallOut } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
@ -27,7 +20,6 @@ interface Props {
windowsCommand: string;
linuxDebCommand: string;
linuxRpmCommand: string;
troubleshootLink: string;
isK8s: boolean;
}
@ -44,7 +36,6 @@ export const PlatformSelector: React.FunctionComponent<Props> = ({
windowsCommand,
linuxDebCommand,
linuxRpmCommand,
troubleshootLink,
isK8s,
}) => {
const { platform, setPlatform } = usePlatform();
@ -127,23 +118,6 @@ export const PlatformSelector: React.FunctionComponent<Props> = ({
)}
</>
)}
<EuiSpacer size="l" />
<EuiText>
<FormattedMessage
id="xpack.fleet.enrollmentInstructions.troubleshootingText"
defaultMessage="If you are having trouble connecting, see our {link}."
values={{
link: (
<EuiLink target="_blank" external href={troubleshootLink}>
<FormattedMessage
id="xpack.fleet.enrollmentInstructions.troubleshootingLink"
defaultMessage="troubleshooting guide"
/>
</EuiLink>
),
}}
/>
</EuiText>
</>
);
};

View file

@ -0,0 +1,51 @@
/*
* 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 { CommandsByPlatform } from '../../../applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils';
import type { K8sMode } from '../../../components/agent_enrollment_flyout/types';
export const StandaloneInstructions = (
kibanaVersion: string,
isK8s?: K8sMode
): CommandsByPlatform => {
const KUBERNETES_RUN_INSTRUCTIONS = 'kubectl apply -f elastic-agent-standalone-kubernetes.yaml';
const STANDALONE_RUN_INSTRUCTIONS_LINUX = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-linux-x86_64.tar.gz
tar xzvf elastic-agent-${kibanaVersion}-linux-x86_64.tar.gz
cd elastic-agent-${kibanaVersion}-linux-x86_64
sudo ./elastic-agent install`;
const STANDALONE_RUN_INSTRUCTIONS_MAC = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-darwin-x86_64.tar.gz
tar xzvf elastic-agent-${kibanaVersion}-darwin-x86_64.tar.gz
cd elastic-agent-${kibanaVersion}-darwin-x86_64
sudo ./elastic-agent install`;
const STANDALONE_RUN_INSTRUCTIONS_WINDOWS = `wget https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-windows-x86_64.zip -OutFile elastic-agent-${kibanaVersion}-windows-x86_64.zip
Expand-Archive .\elastic-agent-${kibanaVersion}-windows-x86_64.zip
cd elastic-agent-${kibanaVersion}-windows-x86_64
.\\elastic-agent.exe install`;
const linuxDebCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-amd64.deb
sudo dpkg -i elastic-agent-${kibanaVersion}-amd64.deb \nsudo systemctl enable elastic-agent \nsudo systemctl start elastic-agent`;
const linuxRpmCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-x86_64.rpm
sudo rpm -vi elastic-agent-${kibanaVersion}-x86_64.rpm \nsudo systemctl enable elastic-agent \nsudo systemctl start elastic-agent`;
const linuxCommand =
isK8s === 'IS_KUBERNETES' ? KUBERNETES_RUN_INSTRUCTIONS : STANDALONE_RUN_INSTRUCTIONS_LINUX;
const macCommand =
isK8s === 'IS_KUBERNETES' ? KUBERNETES_RUN_INSTRUCTIONS : STANDALONE_RUN_INSTRUCTIONS_MAC;
const windowsCommand =
isK8s === 'IS_KUBERNETES' ? KUBERNETES_RUN_INSTRUCTIONS : STANDALONE_RUN_INSTRUCTIONS_WINDOWS;
return {
linux: linuxCommand,
mac: macCommand,
windows: windowsCommand,
deb: linuxDebCommand,
rpm: linuxRpmCommand,
};
};

View file

@ -7,7 +7,6 @@
import React, { useMemo, useState } from 'react';
import { EuiContextMenuItem, EuiPortal } from '@elastic/eui';
import type { EuiStepProps } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { AgentPolicy, InMemoryPackagePolicy } from '../types';
@ -22,14 +21,12 @@ import { PackagePolicyDeleteProvider } from './package_policy_delete_provider';
export const PackagePolicyActionsMenu: React.FunctionComponent<{
agentPolicy: AgentPolicy;
packagePolicy: InMemoryPackagePolicy;
viewDataStep?: EuiStepProps;
showAddAgent?: boolean;
defaultIsOpen?: boolean;
upgradePackagePolicyHref: string;
}> = ({
agentPolicy,
packagePolicy,
viewDataStep,
showAddAgent,
upgradePackagePolicyHref,
defaultIsOpen = false,
@ -43,7 +40,6 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{
const onEnrollmentFlyoutClose = useMemo(() => {
return () => setIsEnrollmentFlyoutOpen(false);
}, []);
const menuItems = [
// FIXME: implement View package policy action
// <EuiContextMenuItem
@ -142,8 +138,12 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{
<EuiPortal>
<AgentEnrollmentFlyout
agentPolicy={agentPolicy}
viewDataStep={viewDataStep}
onClose={onEnrollmentFlyoutClose}
isIntegrationFlow={true}
installedPackagePolicy={{
name: packagePolicy?.package?.name || '',
version: packagePolicy?.package?.version || '',
}}
/>
</EuiPortal>
)}

View file

@ -27,4 +27,3 @@ export * from './use_platform';
export * from './use_agent_policy_refresh';
export * from './use_package_installations';
export * from './use_agent_enrollment_flyout_data';
export * from './use_get_agent_incoming_data';

View file

@ -1,78 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useEffect, useState, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import type { IncomingDataList } from '../../common/types/rest_spec/agent';
import { sendGetAgentIncomingData, useLink } from './index';
export interface InstalledIntegrationPolicy {
name: string;
version: string;
}
export const useGetAgentIncomingData = (
agentsIds: string[],
installedPolicy?: InstalledIntegrationPolicy
) => {
const [isLoading, setIsLoading] = useState<boolean>(true);
const [incomingData, setIncomingData] = useState<IncomingDataList[]>([]);
useEffect(() => {
const getIncomingData = async () => {
const { data } = await sendGetAgentIncomingData({ agentsIds });
if (data?.items) {
setIncomingData(data?.items);
setIsLoading(false);
}
};
if (agentsIds) {
getIncomingData();
}
}, [agentsIds]);
const enrolledAgents = useMemo(() => incomingData.length, [incomingData.length]);
const numAgentsWithData = useMemo(
() =>
incomingData.reduce((acc, curr) => {
const agentData = Object.values(curr)[0];
return !!agentData.data ? acc + 1 : acc;
}, 0),
[incomingData]
);
const { getAbsolutePath, getHref } = useLink();
let href;
let text;
if (!installedPolicy) {
href = '';
text = '';
}
if (installedPolicy?.name === 'apm') {
href = getAbsolutePath('/app/home#/tutorial/apm');
text = i18n.translate('xpack.fleet.confirmIncomingData.installApmAgentButtonText', {
defaultMessage: 'Install APM Agent',
});
} else {
href = getHref('integration_details_assets', {
pkgkey: `${installedPolicy?.name}-${installedPolicy?.version}`,
});
text = i18n.translate('xpack.fleet.confirmIncomingData.viewDataAssetsButtonText', {
defaultMessage: 'View assets',
});
}
const linkButton = { href, text };
return {
enrolledAgents,
numAgentsWithData,
isLoading,
linkButton,
};
};

View file

@ -18,7 +18,7 @@ export const PLATFORM_OPTIONS: Array<{
{
id: 'linux',
label: i18n.translate('xpack.fleet.enrollmentInstructions.platformButtons.linux', {
defaultMessage: 'Linux',
defaultMessage: 'Linux Tar',
}),
'data-test-subj': 'platformTypeLinux',
},

View file

@ -144,7 +144,7 @@ export async function getIncomingDataByAgentsId(
agent_ids: {
terms: {
field: 'agent.id',
size: 10,
size: agentsIds.length,
},
},
},

View file

@ -9843,19 +9843,14 @@
"xpack.fleet.agentEnrollment.downloadLink": "Accéder à la page de téléchargement",
"xpack.fleet.agentEnrollment.downloadPolicyButton": "Télécharger la stratégie",
"xpack.fleet.agentEnrollment.downloadUseLinuxInstaller": "Utilisateurs de Linux : nous vous recommandons d'utiliser les programmes d'installation sur (RPM/DEB), car ils permettent la mise à niveau de votre agent dans Fleet.",
"xpack.fleet.agentEnrollment.enrollFleetTabLabel": "Enregistrer dans Fleet",
"xpack.fleet.agentEnrollment.enrollStandaloneTabLabel": "Exécuter de façon autonome",
"xpack.fleet.agentEnrollment.fleetSettingsLink": "Paramètres de Fleet",
"xpack.fleet.agentEnrollment.flyoutTitle": "Ajouter un agent",
"xpack.fleet.agentEnrollment.goToDataStreamsLink": "flux de données",
"xpack.fleet.agentEnrollment.managedDescription": "L'enregistrement d'un agent Elastic Agent dans Fleet permet de centraliser la gestion de ce dernier tout en déployant automatiquement les mises à jour.",
"xpack.fleet.agentEnrollment.missingFleetHostCalloutText": "L'enregistrement d'agents dans Fleet nécessite l'URL de l'hôte de votre serveur Fleet. Vous pouvez ajouter ces informations dans Paramètres de Fleet. Pour en savoir plus, consultez {link}.",
"xpack.fleet.agentEnrollment.missingFleetHostCalloutTitle": "URL de l'hôte du serveur Fleet manquante",
"xpack.fleet.agentEnrollment.missingFleetHostGuideLink": "Guide de l'utilisateur de Fleet",
"xpack.fleet.agentEnrollment.setUpAgentsLink": "configurer la gestion centralisée des agents Elastic",
"xpack.fleet.agentEnrollment.standaloneDescription": "Exécutez un agent Elastic Agent de façon autonome pour le configurer et le mettre à jour manuellement sur l'hôte sur lequel il est installé.",
"xpack.fleet.agentEnrollment.stepCheckForDataDescription": "L'agent devrait commencer à envoyer des données. Accédez à {link} pour consulter vos données.",
"xpack.fleet.agentEnrollment.stepCheckForDataTitle": "Rechercher des données",
"xpack.fleet.agentEnrollment.stepChooseAgentPolicyTitle": "Sélectionner une stratégie d'agent",
"xpack.fleet.agentEnrollment.stepConfigureAgentDescription": "Copiez cette stratégie dans le fichier {fileName} de l'hôte sur lequel l'agent Elastic Agent est installé. Modifiez {ESUsernameVariable} et {ESPasswordVariable} dans la section {outputSection} du fichier {fileName} pour utiliser vos identifiants de connexion Elasticsearch.",
"xpack.fleet.agentEnrollment.stepConfigureAgentTitle": "Configurer l'agent",
@ -9863,9 +9858,6 @@
"xpack.fleet.agentEnrollment.stepDownloadAgentTitle": "Télécharger l'agent Elastic Agent sur votre hôte",
"xpack.fleet.agentEnrollment.stepEnrollAndRunAgentTitle": "Enregistrer et démarrer l'agent Elastic Agent",
"xpack.fleet.agentEnrollment.stepRunAgentDescription": "Depuis le répertoire des agents, exécutez cette commande pour installer, enregistrer et démarrer un agent Elastic Agent. Vous pouvez réutiliser cette commande pour configurer des agents sur plusieurs hôtes. Cette action nécessite de disposer de privilèges d'administrateur.",
"xpack.fleet.agentEnrollment.stepRunAgentTitle": "Démarrer l'agent",
"xpack.fleet.agentEnrollment.stepViewDataTitle": "Consulter vos données",
"xpack.fleet.agentEnrollment.viewDataDescription": "Une fois que votre agent a démarré, vous pouvez consulter vos données dans Kibana en utilisant les composants de l'intégration installés. {pleaseNote} : l'affichage des données initiales peut prendre quelques minutes.",
"xpack.fleet.agentHealth.checkInTooltipText": "Dernier archivage le {lastCheckIn}",
"xpack.fleet.agentHealth.healthyStatusText": "Sain",
"xpack.fleet.agentHealth.inactiveStatusText": "Inactif",
@ -10133,8 +10125,6 @@
"xpack.fleet.enrollmentTokensList.secretTitle": "Secret",
"xpack.fleet.enrollmentTokensList.showTokenButtonLabel": "Afficher le jeton",
"xpack.fleet.epm.addPackagePolicyButtonText": "Ajouter {packageName}",
"xpack.fleet.epm.agentEnrollment.viewDataAssetsLabel": "Voir les ressources",
"xpack.fleet.epm.agentEnrollment.viewDataDescription.pleaseNoteLabel": "Remarque",
"xpack.fleet.epm.assetGroupTitle": "Ressources {assetType}",
"xpack.fleet.epm.browseAllButtonText": "Parcourir toutes les intégrations",
"xpack.fleet.epm.categoryLabel": "Catégorie",

View file

@ -11540,19 +11540,14 @@
"xpack.fleet.agentEnrollment.downloadPolicyButton": "ポリシーのダウンロード",
"xpack.fleet.agentEnrollment.downloadPolicyButtonk8s": "マニフェストのダウンロード",
"xpack.fleet.agentEnrollment.downloadUseLinuxInstaller": "LinuxユーザーFleetでエージェントをアップグレードできるため、システムパッケージRPM/DEBではインストーラーTARをお勧めします。",
"xpack.fleet.agentEnrollment.enrollFleetTabLabel": "Fleetで登録",
"xpack.fleet.agentEnrollment.enrollStandaloneTabLabel": "スタンドアロンで実行",
"xpack.fleet.agentEnrollment.fleetSettingsLink": "Fleet設定",
"xpack.fleet.agentEnrollment.flyoutTitle": "エージェントの追加",
"xpack.fleet.agentEnrollment.goToDataStreamsLink": "データストリーム",
"xpack.fleet.agentEnrollment.managedDescription": "ElasticエージェントをFleetに登録して、自動的に更新をデプロイしたり、一元的にエージェントを管理したりします。",
"xpack.fleet.agentEnrollment.missingFleetHostCalloutText": "Fleetにエージェントを登録するには、FleetサーバーホストのURLが必要です。Fleet設定でこの情報を追加できます。詳細は{link}をご覧ください。",
"xpack.fleet.agentEnrollment.missingFleetHostCalloutTitle": "FleetサーバーホストのURLが見つかりません",
"xpack.fleet.agentEnrollment.missingFleetHostGuideLink": "FleetおよびElasticエージェントガイド",
"xpack.fleet.agentEnrollment.setUpAgentsLink": "Elasticエージェントの集中管理を設定",
"xpack.fleet.agentEnrollment.standaloneDescription": "Elasticエージェントをスタンドアロンで実行して、エージェントがインストールされているホストで、手動でエージェントを構成および更新します。",
"xpack.fleet.agentEnrollment.stepCheckForDataDescription": "エージェントがデータの送信を開始します。{link}に移動して、データを表示してください。",
"xpack.fleet.agentEnrollment.stepCheckForDataTitle": "データを確認",
"xpack.fleet.agentEnrollment.stepChooseAgentPolicyTitle": "追加しているホストのタイプ",
"xpack.fleet.agentEnrollment.stepConfigureAgentDescription": "Elasticエージェントがインストールされているホストで、このポリシーを{fileName}にコピーします。Elasticsearch資格情報を使用するには、{fileName}の{outputSection}セクションで、{ESUsernameVariable}と{ESPasswordVariable}を変更します。",
"xpack.fleet.agentEnrollment.stepConfigureAgentDescriptionk8s": "Kubernetesクラスター内でKubernetesマニフェストをコピーしてダウンロードします。Daemonset環境変数で{ESUsernameVariable}と{ESPasswordVariable}を修正し、マニフェストを適用します。",
@ -11563,9 +11558,6 @@
"xpack.fleet.agentEnrollment.stepEnrollAndRunAgentTitle": "Elasticエージェントを登録して実行",
"xpack.fleet.agentEnrollment.stepRunAgentDescription": "エージェントのディレクトリから、このコマンドを実行し、Elasticエージェントを、インストール、登録、起動します。このコマンドを再利用すると、複数のホストでエージェントを設定できます。管理者権限が必要です。",
"xpack.fleet.agentEnrollment.stepRunAgentDescriptionk8s": "Kubernetesマニフェストがダウンロードされるディレクトリから適用コマンドを実行します。",
"xpack.fleet.agentEnrollment.stepRunAgentTitle": "エージェントの起動",
"xpack.fleet.agentEnrollment.stepViewDataTitle": "データを表示",
"xpack.fleet.agentEnrollment.viewDataDescription": "エージェントが起動した後、Kibanaでデータを表示するには、統合のインストールされたアセットを使用します。{pleaseNote}:初期データを受信するまでに数分かかる場合があります。",
"xpack.fleet.agentHealth.checkInTooltipText": "前回のチェックイン {lastCheckIn}",
"xpack.fleet.agentHealth.healthyStatusText": "正常",
"xpack.fleet.agentHealth.inactiveStatusText": "非アクティブ",
@ -11864,8 +11856,6 @@
"xpack.fleet.epm.addPackagePolicyButtonPrivilegesRequiredTooltip": "Elasticエージェント統合には、Fleetの「すべて」権限と統合の「すべて」権限が必要です。管理者にお問い合わせください。",
"xpack.fleet.epm.addPackagePolicyButtonSecurityRequiredTooltip": "Elasticエージェント統合を追加するには、セキュリティを有効にし、Fleetの「すべて」権限が必要です。管理者にお問い合わせください。",
"xpack.fleet.epm.addPackagePolicyButtonText": "{packageName}の追加",
"xpack.fleet.epm.agentEnrollment.viewDataAssetsLabel": "アセットを表示",
"xpack.fleet.epm.agentEnrollment.viewDataDescription.pleaseNoteLabel": "注記:",
"xpack.fleet.epm.assetGroupTitle": "{assetType}アセット",
"xpack.fleet.epm.assetTitles.componentTemplates": "コンポーネントテンプレート",
"xpack.fleet.epm.assetTitles.dashboards": "ダッシュボード",

View file

@ -11559,19 +11559,14 @@
"xpack.fleet.agentEnrollment.downloadPolicyButton": "下载策略",
"xpack.fleet.agentEnrollment.downloadPolicyButtonk8s": "下载清单",
"xpack.fleet.agentEnrollment.downloadUseLinuxInstaller": "Linux 用户:我们建议使用安装程序 (TAR),而非系统软件包 (RPM/DEB),因为这允许您在 Fleet 中升级代理。",
"xpack.fleet.agentEnrollment.enrollFleetTabLabel": "在 Fleet 中注册",
"xpack.fleet.agentEnrollment.enrollStandaloneTabLabel": "独立运行",
"xpack.fleet.agentEnrollment.fleetSettingsLink": "Fleet 设置",
"xpack.fleet.agentEnrollment.flyoutTitle": "添加代理",
"xpack.fleet.agentEnrollment.goToDataStreamsLink": "数据流",
"xpack.fleet.agentEnrollment.managedDescription": "在 Fleet 中注册 Elastic 代理,以便自动部署更新并集中管理该代理。",
"xpack.fleet.agentEnrollment.missingFleetHostCalloutText": "需要 Fleet 服务器主机的 URL才能使用 Fleet 注册代理。可以在“Fleet 设置”中添加此信息。有关更多信息,请参阅{link}。",
"xpack.fleet.agentEnrollment.missingFleetHostCalloutTitle": "Fleet 服务器主机的 URL 缺失",
"xpack.fleet.agentEnrollment.missingFleetHostGuideLink": "Fleet 和 Elastic 代理指南",
"xpack.fleet.agentEnrollment.setUpAgentsLink": "为 Elastic 代理设置集中管理",
"xpack.fleet.agentEnrollment.standaloneDescription": "独立运行 Elastic 代理,以在安装代理的主机上手动配置和更新代理。",
"xpack.fleet.agentEnrollment.stepCheckForDataDescription": "该代理应该开始发送数据。前往 {link} 以查看您的数据。",
"xpack.fleet.agentEnrollment.stepCheckForDataTitle": "检查数据",
"xpack.fleet.agentEnrollment.stepChooseAgentPolicyTitle": "您正在添加什么类型的主机?",
"xpack.fleet.agentEnrollment.stepConfigureAgentDescription": "在安装 Elastic 代理的主机上将此策略复制到 {fileName}。在 {fileName} 的 {outputSection} 部分中修改 {ESUsernameVariable} 和 {ESPasswordVariable},以使用您的 Elasticsearch 凭据。",
"xpack.fleet.agentEnrollment.stepConfigureAgentDescriptionk8s": "复制或下载 Kubernetes 集群内的 Kubernetes 清单。修改 Daemonset 环境变量中的 {ESUsernameVariable} 和 {ESPasswordVariable} 并应用该清单。",
@ -11582,9 +11577,6 @@
"xpack.fleet.agentEnrollment.stepEnrollAndRunAgentTitle": "注册并启动 Elastic 代理",
"xpack.fleet.agentEnrollment.stepRunAgentDescription": "从代理目录运行此命令,以安装、注册并启动 Elastic 代理。您可以重复使用此命令在多个主机上设置代理。需要管理员权限。",
"xpack.fleet.agentEnrollment.stepRunAgentDescriptionk8s": "从下载 Kubernetes 清单的目录运行应用命令。",
"xpack.fleet.agentEnrollment.stepRunAgentTitle": "启动代理",
"xpack.fleet.agentEnrollment.stepViewDataTitle": "查看您的数据",
"xpack.fleet.agentEnrollment.viewDataDescription": "代理启动后,可以通过使用集成的已安装资产来在 Kibana 中查看数据。{pleaseNote}:获得初始数据可能需要几分钟。",
"xpack.fleet.agentHealth.checkInTooltipText": "上次签入时间 {lastCheckIn}",
"xpack.fleet.agentHealth.healthyStatusText": "运行正常",
"xpack.fleet.agentHealth.inactiveStatusText": "非活动",
@ -11885,8 +11877,6 @@
"xpack.fleet.epm.addPackagePolicyButtonPrivilegesRequiredTooltip": "Elastic 代理集成需要 Fleet 的所有权限和集成的所有权限。请联系您的管理员。",
"xpack.fleet.epm.addPackagePolicyButtonSecurityRequiredTooltip": "要添加 Elastic 代理集成,必须启用安全功能并具有 Fleet 的所有权限。请联系您的管理员。",
"xpack.fleet.epm.addPackagePolicyButtonText": "添加 {packageName}",
"xpack.fleet.epm.agentEnrollment.viewDataAssetsLabel": "查看资产",
"xpack.fleet.epm.agentEnrollment.viewDataDescription.pleaseNoteLabel": "请注意",
"xpack.fleet.epm.assetGroupTitle": "{assetType} 资产",
"xpack.fleet.epm.assetTitles.componentTemplates": "组件模板",
"xpack.fleet.epm.assetTitles.dashboards": "仪表板",