diff --git a/x-pack/plugins/observability_onboarding/public/application/app.tsx b/x-pack/plugins/observability_onboarding/public/application/app.tsx index 38686c08984f47..0646e0d8009246 100644 --- a/x-pack/plugins/observability_onboarding/public/application/app.tsx +++ b/x-pack/plugins/observability_onboarding/public/application/app.tsx @@ -27,6 +27,7 @@ import ReactDOM from 'react-dom'; import { RouteComponentProps, RouteProps } from 'react-router-dom'; import { ConfigSchema } from '..'; import { customLogsRoutes } from '../components/app/custom_logs/wizard'; +import { systemLogsRoutes } from '../components/app/system_logs'; import { ObservabilityOnboardingHeaderActionMenu } from '../components/app/header_action_menu'; import { ObservabilityOnboardingPluginSetupDeps, @@ -34,6 +35,7 @@ import { } from '../plugin'; import { baseRoutes, routes } from '../routes'; import { CustomLogs } from '../routes/templates/custom_logs'; +import { SystemLogs } from '../routes/templates/system_logs'; export type BreadcrumbTitle< T extends { [K in keyof T]?: string | undefined } = {} @@ -59,6 +61,7 @@ export const breadcrumbsApp = { function App() { const customLogRoutesPaths = Object.keys(customLogsRoutes); + const systemLogRoutesPaths = Object.keys(systemLogsRoutes); return ( <> @@ -94,6 +97,26 @@ function App() { })} + + + {systemLogRoutesPaths.map((key) => { + const path = key as keyof typeof routes; + const { handler, exact } = routes[path]; + const Wrapper = () => { + return handler(); + }; + + return ( + + ); + })} + + ); diff --git a/x-pack/plugins/observability_onboarding/public/components/app/home/index.tsx b/x-pack/plugins/observability_onboarding/public/components/app/home/index.tsx index bd15c9b6159e0e..27a44e94f07b60 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/home/index.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/home/index.tsx @@ -56,7 +56,9 @@ export function Home() { const { navigateToKibanaUrl } = useKibanaNavigation(); - const handleClickSystemLogs = () => {}; + const handleClickSystemLogs = () => { + navigateToKibanaUrl('/app/observabilityOnboarding/systemLogs'); + }; const handleClickCustomLogs = () => { navigateToKibanaUrl('/app/observabilityOnboarding/customLogs'); }; diff --git a/x-pack/plugins/observability_onboarding/public/components/app/system_logs/index.tsx b/x-pack/plugins/observability_onboarding/public/components/app/system_logs/index.tsx new file mode 100644 index 00000000000000..b5c866eec0e6e8 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/public/components/app/system_logs/index.tsx @@ -0,0 +1,52 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { + createWizardContext, + Step, +} from '../../../context/create_wizard_context'; +import { InstallElasticAgent } from './install_elastic_agent'; + +interface WizardState { + elasticAgentPlatform: 'linux-tar' | 'macos' | 'windows'; + autoDownloadConfig: boolean; + apiKeyEncoded: string; + onboardingId: string; +} + +const initialState: WizardState = { + elasticAgentPlatform: 'linux-tar', + autoDownloadConfig: false, + apiKeyEncoded: '', + onboardingId: '', +}; + +export type SystemLogsSteps = 'installElasticAgent'; + +const steps: Record = { + installElasticAgent: { + component: InstallElasticAgent, + title: i18n.translate( + 'xpack.observability_onboarding.systemLogs.installShipper.title', + { defaultMessage: 'Install shipper to collect system logs' } + ), + }, +}; + +const { + Provider, + useWizard, + routes: systemLogsRoutes, +} = createWizardContext({ + initialState, + initialStep: 'installElasticAgent', + steps, + basePath: '/systemLogs', +}); + +export { Provider, useWizard, systemLogsRoutes }; diff --git a/x-pack/plugins/observability_onboarding/public/components/app/system_logs/install_elastic_agent.tsx b/x-pack/plugins/observability_onboarding/public/components/app/system_logs/install_elastic_agent.tsx new file mode 100644 index 00000000000000..2120686c0f1322 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/public/components/app/system_logs/install_elastic_agent.tsx @@ -0,0 +1,319 @@ +/* + * 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 { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { default as React, useCallback, useEffect, useState } from 'react'; +import { useWizard } from '.'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useKibanaNavigation } from '../../../hooks/use_kibana_navigation'; +import { + ElasticAgentPlatform, + getElasticAgentSetupCommand, +} from '../../shared/get_elastic_agent_setup_command'; +import { + InstallElasticAgentSteps, + ProgressStepId, + EuiStepStatus, +} from '../../shared/install_elastic_agent_steps'; +import { + StepPanel, + StepPanelContent, + StepPanelFooter, +} from '../../shared/step_panel'; +import { ApiKeyBanner } from '../custom_logs/wizard/api_key_banner'; + +export function InstallElasticAgent() { + const { navigateToKibanaUrl } = useKibanaNavigation(); + const { getState, setState } = useWizard(); + const wizardState = getState(); + const [elasticAgentPlatform, setElasticAgentPlatform] = + useState('linux-tar'); + + const datasetName = 'elastic-agent'; + const namespace = 'default'; + + function onBack() { + navigateToKibanaUrl('/app/observabilityOnboarding'); + } + function onContinue() { + navigateToKibanaUrl('/app/logs/stream'); + } + + function onAutoDownloadConfig() { + setState((state) => ({ + ...state, + autoDownloadConfig: !state.autoDownloadConfig, + })); + } + + const { data: monitoringRole, status: monitoringRoleStatus } = useFetcher( + (callApi) => { + return callApi( + 'GET /internal/observability_onboarding/custom_logs/privileges' + ); + }, + [] + ); + + const { data: setup } = useFetcher((callApi) => { + return callApi( + 'GET /internal/observability_onboarding/custom_logs/install_shipper_setup' + ); + }, []); + + const { + data: installShipperSetup, + status: installShipperSetupStatus, + error, + } = useFetcher( + (callApi) => { + if (monitoringRole?.hasPrivileges) { + return callApi( + 'POST /internal/observability_onboarding/custom_logs/save', + { + params: { + body: { + name: datasetName, + state: { + datasetName, + namespace, + }, + }, + }, + } + ); + } + }, + [monitoringRole?.hasPrivileges] + ); + + const { status: saveOnboardingStateDataStatus } = useFetcher((callApi) => { + const { onboardingId } = getState(); + if (onboardingId) { + return callApi( + 'PUT /internal/observability_onboarding/custom_logs/{onboardingId}/save', + { + params: { + path: { onboardingId }, + body: { + state: { + datasetName, + namespace, + }, + }, + }, + } + ); + } + }, []); + + const { apiKeyEncoded, onboardingId } = installShipperSetup ?? getState(); + + const { data: yamlConfig = '', status: yamlConfigStatus } = useFetcher( + (callApi) => { + if (apiKeyEncoded && onboardingId) { + return callApi( + 'GET /internal/observability_onboarding/elastic_agent/config', + { + headers: { authorization: `ApiKey ${apiKeyEncoded}` }, + params: { query: { onboardingId } }, + } + ); + } + }, + [ + apiKeyEncoded, + onboardingId, + saveOnboardingStateDataStatus === FETCH_STATUS.SUCCESS, + ] + ); + + useEffect(() => { + setState((state) => ({ ...state, onboardingId, apiKeyEncoded })); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [onboardingId, apiKeyEncoded]); + + const { + data: progressData, + status: progressStatus, + refetch: refetchProgress, + } = useFetcher( + (callApi) => { + if (onboardingId) { + return callApi( + 'GET /internal/observability_onboarding/custom_logs/{onboardingId}/progress', + { params: { path: { onboardingId } } } + ); + } + }, + [onboardingId] + ); + + const progressSucceded = progressStatus === FETCH_STATUS.SUCCESS; + + useEffect(() => { + if (progressSucceded) { + setTimeout(() => { + refetchProgress(); + }, 2000); + } + }, [progressSucceded, refetchProgress]); + + const getCheckLogsStep = useCallback(() => { + const progress = progressData?.progress; + if (progress) { + const stepStatus = progress?.['logs-ingest']?.status as EuiStepStatus; + const title = + stepStatus === 'loading' + ? CHECK_LOGS_LABELS.loading + : stepStatus === 'complete' + ? CHECK_LOGS_LABELS.completed + : CHECK_LOGS_LABELS.incomplete; + return { title, status: stepStatus }; + } + return { + title: CHECK_LOGS_LABELS.incomplete, + status: 'incomplete' as const, + }; + }, [progressData?.progress]); + + const isInstallStarted = progressData?.progress['ea-download'] !== undefined; + const isInstallCompleted = + progressData?.progress?.['ea-status']?.status === 'complete'; + const autoDownloadConfigStatus = progressData?.progress?.['ea-config'] + ?.status as EuiStepStatus; + + return ( + + {i18n.translate( + 'xpack.observability_onboarding.systemLogs.back', + { defaultMessage: 'Back' } + )} + , + + + + {i18n.translate( + 'xpack.observability_onboarding.steps.exploreLogs', + { defaultMessage: 'Explore logs' } + )} + + + , + ]} + /> + } + > + + +

+ {i18n.translate( + 'xpack.observability_onboarding.systemLogs.installElasticAgent.description', + { + defaultMessage: + 'To collect the data from your system and stream it to Elastic, you first need to install a shipping tool on the machine generating the logs. In this case, the shipper is an Agent developed by Elastic.', + } + )} +

+
+ + {apiKeyEncoded && onboardingId ? ( + + ) : ( + monitoringRoleStatus !== FETCH_STATUS.NOT_INITIATED && + monitoringRoleStatus !== FETCH_STATUS.LOADING && ( + + ) + )} + + setElasticAgentPlatform(id)} + selectedPlatform={elasticAgentPlatform} + installAgentCommand={getElasticAgentSetupCommand({ + elasticAgentPlatform, + apiKeyEncoded, + apiEndpoint: setup?.apiEndpoint, + scriptDownloadUrl: setup?.scriptDownloadUrl, + elasticAgentVersion: setup?.elasticAgentVersion, + autoDownloadConfig: wizardState.autoDownloadConfig, + onboardingId, + })} + autoDownloadConfig={wizardState.autoDownloadConfig} + onToggleAutoDownloadConfig={onAutoDownloadConfig} + installAgentStatus={ + installShipperSetupStatus === FETCH_STATUS.LOADING + ? 'loading' + : isInstallCompleted + ? 'complete' + : 'current' + } + showInstallProgressSteps={isInstallStarted} + installProgressSteps={ + (progressData?.progress ?? {}) as Partial< + Record< + ProgressStepId, + { status: EuiStepStatus; message?: string } + > + > + } + configureAgentStatus={ + yamlConfigStatus === FETCH_STATUS.LOADING + ? 'loading' + : autoDownloadConfigStatus + } + configureAgentYaml={yamlConfig} + appendedSteps={[getCheckLogsStep()]} + /> +
+
+ ); +} + +const CHECK_LOGS_LABELS = { + incomplete: i18n.translate( + 'xpack.observability_onboarding.systemLogs.installElasticAgent.progress.logsIngest.incompleteTitle', + { defaultMessage: 'Ship logs to Elastic Observability' } + ), + loading: i18n.translate( + 'xpack.observability_onboarding.systemLogs.installElasticAgent.progress.logsIngest.loadingTitle', + { defaultMessage: 'Waiting for logs to be shipped...' } + ), + completed: i18n.translate( + 'xpack.observability_onboarding.systemLogs.installElasticAgent.progress.logsIngest.completedTitle', + { defaultMessage: 'Logs are being shipped!' } + ), +}; diff --git a/x-pack/plugins/observability_onboarding/public/routes/index.tsx b/x-pack/plugins/observability_onboarding/public/routes/index.tsx index 0ad61cce116f1e..66a99343be2b21 100644 --- a/x-pack/plugins/observability_onboarding/public/routes/index.tsx +++ b/x-pack/plugins/observability_onboarding/public/routes/index.tsx @@ -9,6 +9,7 @@ import * as t from 'io-ts'; import React from 'react'; import { Redirect } from 'react-router-dom'; import { customLogsRoutes } from '../components/app/custom_logs/wizard'; +import { systemLogsRoutes } from '../components/app/system_logs'; import { Home } from '../components/app/home'; export type RouteParams = DecodeParams< @@ -42,4 +43,5 @@ export const baseRoutes = { export const routes = { ...baseRoutes, ...customLogsRoutes, + ...systemLogsRoutes, }; diff --git a/x-pack/plugins/observability_onboarding/public/routes/templates/system_logs.tsx b/x-pack/plugins/observability_onboarding/public/routes/templates/system_logs.tsx new file mode 100644 index 00000000000000..7881914d053d81 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/public/routes/templates/system_logs.tsx @@ -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 { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; +import React from 'react'; +import { breadcrumbsApp } from '../../application/app'; +import { Provider as WizardProvider } from '../../components/app/system_logs'; + +interface Props { + children: React.ReactNode; +} + +export function SystemLogs({ children }: Props) { + useBreadcrumbs( + [ + { + text: i18n.translate( + 'xpack.observability_onboarding.breadcrumbs.systemLogs', + { defaultMessage: 'System logs' } + ), + }, + ], + breadcrumbsApp + ); + return ( + + + + + +

+ {i18n.translate( + 'xpack.observability_onboarding.title.collectSystemLogs', + { defaultMessage: 'Install shipper to collect system logs' } + )} +

+
+
+ + {children} + +
+
+ ); +}