[Observability Onboarding] Auto-detect flow (#186106)

Resolves #183362

## Summary

Implements auto-detect based onboarding flow.
The new flow can only be accessed behind a hidden URL:
`/app/observabilityOnboarding/auto-detect?category=logs`

## Screenshots

### Kibana

<img width="1243" alt="Screenshot 2024-06-12 at 16 05 54"
src="ab4d01a4-431b-425e-acc1-80cb7adadb35">

### Terminal

<img width="1188" alt="Screenshot 2024-06-12 at 16 07 32"
src="e5924156-d2c5-487d-af2f-163c3d114e93">

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Joe Reuter <johannes.reuter@elastic.co>
This commit is contained in:
Thom Heymann 2024-06-24 11:35:01 +01:00 committed by GitHub
parent 8bd7124ca1
commit f015066fcf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1443 additions and 265 deletions

View file

@ -1,22 +1,37 @@
{
"type": "plugin",
"id": "@kbn/observability-onboarding-plugin",
"owner": ["@elastic/obs-ux-logs-team", "@elastic/obs-ux-onboarding-team"],
"owner": [
"@elastic/obs-ux-logs-team",
"@elastic/obs-ux-onboarding-team"
],
"plugin": {
"id": "observabilityOnboarding",
"server": true,
"browser": true,
"configPath": ["xpack", "observability_onboarding"],
"configPath": [
"xpack",
"observability_onboarding"
],
"requiredPlugins": [
"data",
"observability",
"observabilityShared",
"discover",
"share",
"fleet"
"fleet",
"security"
],
"optionalPlugins": ["cloud", "cloudExperiments", "usageCollection"],
"requiredBundles": ["kibanaReact"],
"extraPublicDirs": ["common"]
"optionalPlugins": [
"cloud",
"cloudExperiments",
"usageCollection"
],
"requiredBundles": [
"kibanaReact"
],
"extraPublicDirs": [
"common"
]
}
}
}

View file

@ -22,6 +22,7 @@ import { ObservabilityOnboardingHeaderActionMenu } from './shared/header_action_
import {
ObservabilityOnboardingPluginSetupDeps,
ObservabilityOnboardingPluginStartDeps,
ObservabilityOnboardingContextValue,
} from '../plugin';
import { ObservabilityOnboardingFlow } from './observability_onboarding_flow';
@ -40,14 +41,13 @@ export const breadcrumbsApp = {
export function ObservabilityOnboardingAppRoot({
appMountParameters,
core,
deps,
corePlugins: { observability, data },
corePlugins,
config,
}: {
appMountParameters: AppMountParameters;
} & RenderAppProps) {
const { history, setHeaderActionMenu, theme$ } = appMountParameters;
const plugins = { ...deps };
const services: ObservabilityOnboardingContextValue = { ...core, ...corePlugins, config };
const renderFeedbackLinkAsPortal = !config.serverless.enabled;
@ -63,15 +63,7 @@ export function ObservabilityOnboardingAppRoot({
application: core.application,
}}
>
<KibanaContextProvider
services={{
...core,
...plugins,
observability,
data,
config,
}}
>
<KibanaContextProvider services={services}>
<KibanaThemeProvider
theme={{ theme$ }}
modify={{

View file

@ -17,6 +17,7 @@ import { OnboardingFlowForm } from './onboarding_flow_form/onboarding_flow_form'
import { Header } from './header/header';
import { SystemLogsPanel } from './quickstart_flows/system_logs';
import { CustomLogsPanel } from './quickstart_flows/custom_logs';
import { AutoDetectPanel } from './quickstart_flows/auto_detect';
import { BackButton } from './shared/back_button';
const queryClient = new QueryClient();
@ -52,6 +53,10 @@ export function ObservabilityOnboardingFlow() {
</EuiPageTemplate.Section>
<EuiPageTemplate.Section paddingSize="xl" color="subdued" restrictWidth>
<Routes>
<Route path="/auto-detect">
<BackButton />
<AutoDetectPanel />
</Route>
<Route path="/systemLogs">
<BackButton />
<SystemLogsPanel />

View file

@ -0,0 +1,239 @@
/*
* 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, { type FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiPanel,
EuiSteps,
EuiCodeBlock,
EuiSpacer,
EuiSkeletonText,
EuiBadge,
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiImage,
EuiSkeletonRectangle,
useGeneratedHtmlId,
} from '@elastic/eui';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import {
type SingleDatasetLocatorParams,
SINGLE_DATASET_LOCATOR_ID,
} from '@kbn/deeplinks-observability/locators';
import { type DashboardLocatorParams } from '@kbn/dashboard-plugin/public';
import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
import { getAutoDetectCommand } from './get_auto_detect_command';
import { useOnboardingFlow } from './use_onboarding_flow';
import { ProgressIndicator } from '../shared/progress_indicator';
import { AccordionWithIcon } from '../shared/accordion_with_icon';
import { type ObservabilityOnboardingContextValue } from '../../../plugin';
import { EmptyPrompt } from '../shared/empty_prompt';
import { CopyToClipboardButton } from '../shared/copy_to_clipboard_button';
import { LocatorButtonEmpty } from '../shared/locator_button_empty';
export const AutoDetectPanel: FunctionComponent = () => {
const {
services: { http },
} = useKibana<ObservabilityOnboardingContextValue>();
const { status, data, error, refetch, installedIntegrations } = useOnboardingFlow();
const command = data ? getAutoDetectCommand(data) : undefined;
const accordionId = useGeneratedHtmlId({ prefix: 'accordion' });
if (error) {
return <EmptyPrompt error={error} onRetryClick={refetch} />;
}
const registryIntegrations = installedIntegrations.filter(
(integration) => integration.installSource === 'registry'
);
const customIntegrations = installedIntegrations.filter(
(integration) => integration.installSource === 'custom'
);
return (
<EuiPanel hasBorder paddingSize="xl">
<EuiSteps
steps={[
{
title: i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.runTheCommandOnLabel',
{ defaultMessage: 'Run the command on your host' }
),
status: status === 'notStarted' ? 'current' : 'complete',
children: command ? (
<>
<EuiText>
<p>
{i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.p.wellScanYourHostLabel',
{
defaultMessage: "We'll scan your host for logs and metrics, including:",
}
)}
</p>
</EuiText>
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="s">
{['Apache', 'Docker', 'Nginx', 'System', 'Custom .log files'].map((item) => (
<EuiFlexItem key={item} grow={false}>
<EuiBadge color="hollow">{item}</EuiBadge>
</EuiFlexItem>
))}
</EuiFlexGroup>
<EuiSpacer />
{/* Bash syntax highlighting only highlights a few random numbers (badly) so it looks less messy to go with plain text */}
<EuiCodeBlock paddingSize="m" language="text">
{command}
</EuiCodeBlock>
<EuiSpacer />
<CopyToClipboardButton textToCopy={command} fill={status === 'notStarted'} />
</>
) : (
<EuiSkeletonText lines={6} />
),
},
{
title: i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.visualizeYourDataLabel',
{ defaultMessage: 'Visualize your data' }
),
status:
status === 'dataReceived'
? 'complete'
: status === 'awaitingData' || status === 'inProgress'
? 'current'
: 'incomplete',
children: (
<>
{status === 'dataReceived' ? (
<ProgressIndicator
iconType="cheer"
title={i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.yourDataIsReadyToExploreLabel',
{ defaultMessage: 'Your data is ready to explore!' }
)}
isLoading={false}
/>
) : status === 'awaitingData' ? (
<ProgressIndicator
title={i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.installingElasticAgentFlexItemLabel',
{ defaultMessage: 'Waiting for data to arrive...' }
)}
/>
) : status === 'inProgress' ? (
<ProgressIndicator
title={i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.lookingForLogFilesFlexItemLabel',
{ defaultMessage: 'Waiting for installation to complete...' }
)}
/>
) : null}
{(status === 'awaitingData' || status === 'dataReceived') &&
installedIntegrations.length > 0 ? (
<>
<EuiSpacer />
{registryIntegrations.map((integration) => (
<AccordionWithIcon
key={integration.pkgName}
id={`${accordionId}_${integration.pkgName}`}
iconType="desktop"
title={i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.h3.getStartedWithNginxLabel',
{
defaultMessage: 'Get started with {title} logs',
values: { title: integration.title },
}
)}
isDisabled={status !== 'dataReceived'}
initialIsOpen
>
<EuiFlexGroup responsive={false}>
<EuiFlexItem grow={false}>
{status === 'dataReceived' ? (
<EuiImage
src={http.staticAssets.getPluginAssetHref('charts_screen.svg')}
width={162}
height={117}
alt=""
hasShadow
/>
) : (
<EuiSkeletonRectangle width={162} height={117} />
)}
</EuiFlexItem>
<EuiFlexItem>
<ul>
{integration.kibanaAssets
.filter((asset) => asset.type === 'dashboard')
.map((dashboard) => (
<li key={dashboard.id}>
<LocatorButtonEmpty<DashboardLocatorParams>
locator={DASHBOARD_APP_LOCATOR}
params={{ dashboardId: dashboard.id }}
target="_blank"
iconType="dashboardApp"
isDisabled={status !== 'dataReceived'}
flush="left"
size="s"
>
{dashboard.attributes.title}
</LocatorButtonEmpty>
</li>
))}
</ul>
</EuiFlexItem>
</EuiFlexGroup>
</AccordionWithIcon>
))}
{customIntegrations.length > 0 && (
<AccordionWithIcon
id={`${accordionId}_custom`}
iconType="documents"
title={i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.h3.getStartedWithlogLabel',
{ defaultMessage: 'Get started with custom .log files' }
)}
isDisabled={status !== 'dataReceived'}
initialIsOpen
>
<ul>
{customIntegrations.map((integration) =>
integration.dataStreams.map((datastream) => (
<li key={`${integration.pkgName}/${datastream.dataset}`}>
<LocatorButtonEmpty<SingleDatasetLocatorParams>
locator={SINGLE_DATASET_LOCATOR_ID}
params={{
integration: integration.pkgName,
dataset: datastream.dataset,
}}
target="_blank"
iconType="document"
isDisabled={status !== 'dataReceived'}
flush="left"
size="s"
>
{integration.pkgName}
</LocatorButtonEmpty>
</li>
))
)}
</ul>
</AccordionWithIcon>
)}
</>
) : null}
</>
),
},
]}
/>
</EuiPanel>
);
};

View file

@ -0,0 +1,28 @@
/*
* 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 { flatten, zip } from 'lodash';
import { useOnboardingFlow } from './use_onboarding_flow';
export function getAutoDetectCommand(
options: NonNullable<ReturnType<typeof useOnboardingFlow>['data']>
) {
const scriptName = 'auto_detect.sh';
return oneLine`
curl ${options.scriptDownloadUrl} -so ${scriptName} &&
sudo bash ${scriptName}
--id=${options.onboardingFlow.id}
--kibana-url=${options.kibanaUrl}
--install-key=${options.installApiKey}
--ingest-key=${options.ingestApiKey}
--ea-version=${options.elasticAgentVersion}
`;
}
function oneLine(parts: TemplateStringsArray, ...args: string[]) {
const str = flatten(zip(parts, args)).join('');
return str.replace(/\s+/g, ' ').trim();
}

View file

@ -0,0 +1,16 @@
/*
* 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 { InstallIntegrationsStepPayload } from '../../../../server/routes/types';
import type { ObservabilityOnboardingFlow } from '../../../../server/saved_objects/observability_onboarding_status';
import type { InstalledIntegration } from '../../../../server/routes/types';
export function getInstalledIntegrations(
data: Pick<ObservabilityOnboardingFlow, 'progress'> | undefined
): InstalledIntegration[] {
return (data?.progress['install-integrations']?.payload as InstallIntegrationsStepPayload) ?? [];
}

View file

@ -0,0 +1,37 @@
/*
* 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 { ObservabilityOnboardingFlow } from '../../../../server/saved_objects/observability_onboarding_status';
export type ObservabilityOnboardingFlowStatus =
| 'notStarted'
| 'inProgress'
| 'awaitingData'
| 'dataReceived';
/**
* Returns the current status of the onboarding flow:
*
* - `notStarted`: No progress has been made.
* - `inProgress`: The user is running the installation command on the host.
* - `awaitingData`: The installation has completed and we are waiting for data to be ingested.
* - `dataReceived`: Data has been ingested - The Agent is up and running.
*/
export function getOnboardingStatus(
data: Pick<ObservabilityOnboardingFlow, 'progress'> | undefined
): ObservabilityOnboardingFlowStatus {
if (!data) {
return 'notStarted';
}
return data.progress['logs-ingest']?.status === 'complete'
? 'dataReceived'
: data.progress['logs-ingest']?.status === 'loading'
? 'awaitingData'
: Object.values(data.progress).some((step) => step.status !== 'incomplete')
? 'inProgress'
: 'notStarted';
}

View file

@ -0,0 +1,8 @@
/*
* 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 { AutoDetectPanel } from './auto_detect_panel';

View file

@ -0,0 +1,97 @@
/*
* 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 useInterval from 'react-use/lib/useInterval';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import useAsync from 'react-use/lib/useAsync';
import { type AssetSOObject, type GetBulkAssetsResponse } from '@kbn/fleet-plugin/common';
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
import { getOnboardingStatus } from './get_onboarding_status';
import { getInstalledIntegrations } from './get_installed_integrations';
import { type ObservabilityOnboardingContextValue } from '../../../plugin';
export function useOnboardingFlow() {
const {
services: { fleet },
} = useKibana<ObservabilityOnboardingContextValue>();
// Create onboarding session
const { data, error, refetch } = useFetcher(
(callApi) =>
callApi('POST /internal/observability_onboarding/flow', {
params: {
body: {
name: 'auto-detect',
},
},
}),
[],
{ showToastOnError: false }
);
const onboardingId = data?.onboardingFlow.id;
// Fetch onboarding progress
const {
data: progressData,
status: progressStatus,
refetch: refetchProgress,
} = useFetcher(
(callApi) => {
if (onboardingId) {
return callApi('GET /internal/observability_onboarding/flow/{onboardingId}/progress', {
params: { path: { onboardingId } },
});
}
},
[onboardingId]
);
const status = getOnboardingStatus(progressData);
const installedIntegrations = getInstalledIntegrations(progressData);
// Fetch metadata for installed Kibana assets
const assetsState = useAsync(async () => {
if (installedIntegrations.length === 0) {
return [];
}
const assetsMetadata = await fleet.hooks.epm.getBulkAssets({
assetIds: installedIntegrations
.map((integration) => integration.kibanaAssets)
.flat() as AssetSOObject[],
});
return installedIntegrations.map((integration) => {
return {
...integration,
// Enrich installed Kibana assets with metadata from Fleet API (e.g. title, description, etc.)
kibanaAssets: integration.kibanaAssets.reduce<GetBulkAssetsResponse['items']>(
(acc, asset) => {
const assetWithMetadata = assetsMetadata.data?.items.find(({ id }) => id === asset.id);
if (assetWithMetadata) {
acc.push(assetWithMetadata);
}
return acc;
},
[]
),
};
});
}, [installedIntegrations.length]); // eslint-disable-line react-hooks/exhaustive-deps
useInterval(
refetchProgress,
progressStatus === FETCH_STATUS.SUCCESS && status !== 'dataReceived' ? 3000 : null
);
return {
data,
error,
refetch,
status,
installedIntegrations: assetsState.value ?? [],
};
}

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 React, { type FunctionComponent } from 'react';
import {
EuiAccordion,
EuiIcon,
EuiTitle,
EuiFlexGroup,
EuiFlexItem,
type EuiAccordionProps,
} from '@elastic/eui';
interface AccordionWithIconProps
extends Omit<EuiAccordionProps, 'buttonContent' | 'buttonProps' | 'borders' | 'paddingSize'> {
title: string;
iconType: string;
}
export const AccordionWithIcon: FunctionComponent<AccordionWithIconProps> = ({
title,
iconType,
children,
...rest
}) => {
return (
<EuiAccordion
{...rest}
buttonContent={
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false} css={{ marginLeft: 8 }}>
<EuiFlexItem grow={false}>
<EuiIcon type={iconType} size="l" />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="xs" css={rest.isDisabled ? { color: 'inherit' } : undefined}>
<h3>{title}</h3>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
}
buttonProps={{ paddingSize: 'l' }}
borders="horizontal"
paddingSize="none"
>
<div css={{ paddingLeft: 36, paddingBottom: 24 }}>{children}</div>
</EuiAccordion>
);
};

View file

@ -0,0 +1,39 @@
/*
* 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, { type FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiCopy, EuiButton, type EuiButtonProps } from '@elastic/eui';
interface CopyToClipboardButtonProps extends Omit<EuiButtonProps, 'onClick'> {
textToCopy: string;
}
export const CopyToClipboardButton: FunctionComponent<CopyToClipboardButtonProps> = ({
textToCopy,
children,
...rest
}) => {
return (
<EuiCopy textToCopy={textToCopy}>
{(copyToClipboard) => (
<EuiButton
data-test-subj="observabilityOnboardingCopyToClipboardButton"
iconType="copyClipboard"
onClick={copyToClipboard}
{...rest}
>
{children ??
i18n.translate(
'xpack.observability_onboarding.copyToClipboardButton.copyToClipboardButtonLabel',
{ defaultMessage: 'Copy to clipboard' }
)}
</EuiButton>
)}
</EuiCopy>
);
};

View file

@ -0,0 +1,85 @@
/*
* 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, { type FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser';
interface EmptyPromptProps {
error: IHttpFetchError<ResponseErrorBody>;
onRetryClick(): void;
}
export const EmptyPrompt: FunctionComponent<EmptyPromptProps> = ({ error, onRetryClick }) => {
if (error.response?.status === 403) {
return (
<EuiEmptyPrompt
color="plain"
iconType="lock"
title={
<h2>
{i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.h2.contactYourAdministratorForLabel',
{ defaultMessage: 'Contact your administrator for access' }
)}
</h2>
}
body={
<p>
{i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.p.toInstallIntegrationsAndLabel',
{
defaultMessage:
'To install integrations and ingest data, you need additional privileges.',
}
)}
</p>
}
/>
);
}
return (
<EuiEmptyPrompt
color="danger"
iconType="error"
title={
<h2>
{i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.h2.unableToInitiateDataLabel',
{ defaultMessage: 'Unable to load content' }
)}
</h2>
}
body={
<p>
{i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.p.thereWasAProblemLabel',
{
defaultMessage:
'There was a problem loading the application. Retry or contact your administrator for help.',
}
)}
</p>
}
actions={
<EuiButton
color="danger"
iconType="refresh"
fill
data-test-subj="observabilityOnboardingAutoDetectPanelGoBackButton"
onClick={onRetryClick}
>
{i18n.translate(
'xpack.observability_onboarding.autoDetectPanel.backToSelectionButtonLabel',
{ defaultMessage: 'Retry' }
)}
</EuiButton>
}
/>
);
};

View file

@ -0,0 +1,78 @@
/*
* 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, { type AnchorHTMLAttributes } from 'react';
import { EuiButtonEmpty, type EuiButtonEmptyProps } from '@elastic/eui';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import type { SerializableRecord } from '@kbn/utility-types';
import { type LocatorPublic } from '@kbn/share-plugin/common';
import { type ObservabilityOnboardingContextValue } from '../../../plugin';
type EuiButtonEmptyPropsForAnchor = Extract<
EuiButtonEmptyProps,
AnchorHTMLAttributes<HTMLAnchorElement>
>;
export interface LocatorButtonEmptyProps<Params extends SerializableRecord>
extends Omit<EuiButtonEmptyPropsForAnchor, 'href'> {
locator: string | LocatorPublic<Params>;
params: Params;
}
/**
* Same as `EuiButtonEmpty` but uses locators to navigate instead of URLs.
*
* Accepts the following props instead of an `href`:
* - `locator`: Either the URL locator public contract or the ID of the locator if previously registered.
* - `params`: The params to pass to the locator.
*
* Get type safety for `params` by passing the correct type to the generic component.
*
* Example 1:
*
* ```ts
* <LocatorButtonEmpty locator={dashboardStart.locator} params={{ dashboardId: 'abc' }}>
* View dashboard
* </LocatorButtonEmpty>
* ```
*
* Example 2:
*
* ```ts
* import { type SingleDatasetLocatorParams, SINGLE_DATASET_LOCATOR_ID } from '@kbn/deeplinks-observability/locators';
*
* <LocatorButtonEmpty<SingleDatasetLocatorParams>
* locator={SINGLE_DATASET_LOCATOR_ID}
* params={{
* integration: 'system',
* dataset: 'system.syslog',
* }}
* >
* View in Logs Explorer
* </LocatorButtonEmpty>
* ```
*/
export const LocatorButtonEmpty = <Params extends SerializableRecord>({
locator,
params,
...rest
}: LocatorButtonEmptyProps<Params>) => {
const {
services: { share },
} = useKibana<ObservabilityOnboardingContextValue>();
const locatorObj =
typeof locator === 'string' ? share.url.locators.get<Params>(locator) : locator;
return (
<EuiButtonEmpty
data-test-subj="observabilityOnboardingLocatorButtonEmptyButton"
href={locatorObj?.useUrl(params)}
{...rest}
/>
);
};

View file

@ -0,0 +1,49 @@
/*
* 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, { type FunctionComponent } from 'react';
import {
EuiIcon,
EuiFlexGroup,
EuiFlexItem,
type EuiCallOutProps,
EuiCallOut,
EuiLoadingSpinner,
} from '@elastic/eui';
interface ProgressIndicatorProps extends EuiCallOutProps {
iconType?: string;
isLoading?: boolean;
}
export const ProgressIndicator: FunctionComponent<ProgressIndicatorProps> = ({
iconType,
isLoading = true,
title,
color = isLoading ? 'primary' : 'success',
...rest
}) => {
return (
<EuiCallOut
title={
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
{isLoading ? (
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size={rest.size} />
</EuiFlexItem>
) : iconType ? (
<EuiFlexItem grow={false}>
<EuiIcon type={iconType} color={color} size={rest.size} />
</EuiFlexItem>
) : null}
<EuiFlexItem>{title}</EuiFlexItem>
</EuiFlexGroup>
}
color={color}
{...rest}
/>
);
};

View file

@ -87,9 +87,9 @@ selected_unknown_log_file_pattern_array=()
excluded_options_string=""
selected_unknown_log_file_pattern_tsv_string=""
custom_log_file_path_list_tsv_string=""
install_integrations_api_body_string=""
elastic_agent_artifact_name=""
elastic_agent_config_path="/opt/Elastic/Agent/elastic-agent.yml"
elastic_agent_tmp_config_path="/tmp/elastic-agent-config-template.yml"
OS="$(uname)"
ARCH="$(uname -m)"
@ -130,14 +130,16 @@ update_step_progress() {
--header "x-elastic-internal-origin: Kibana" \
--data "$data" \
--output /dev/null \
--no-progress-meter
--no-progress-meter \
--fail
}
download_elastic_agent() {
local download_url="https://artifacts.elastic.co/downloads/beats/elastic-agent/${elastic_agent_artifact_name}.tar.gz"
curl -L -O $download_url --fail
curl -L -O $download_url --silent --fail
if [ "$?" -eq 0 ]; then
printf "\e[1;32m✓\e[0m %s\n" "Elastic Agent downloaded to $(pwd)/$elastic_agent_artifact_name.tar.gz"
update_step_progress "ea-download" "complete"
else
update_step_progress "ea-download" "danger" "Failed to download Elastic Agent, see script output for error."
@ -149,6 +151,7 @@ extract_elastic_agent() {
tar -xzf "${elastic_agent_artifact_name}.tar.gz"
if [ "$?" -eq 0 ]; then
printf "\e[1;32m✓\e[0m %s\n" "Archive extracted"
update_step_progress "ea-extract" "complete"
else
update_step_progress "ea-extract" "danger" "Failed to extract Elastic Agent, see script output for error."
@ -157,9 +160,10 @@ extract_elastic_agent() {
}
install_elastic_agent() {
"./${elastic_agent_artifact_name}/elastic-agent" install -f
"./${elastic_agent_artifact_name}/elastic-agent" install -f -n > /dev/null
if [ "$?" -eq 0 ]; then
printf "\e[1;32m✓\e[0m %s\n" "Elastic Agent installed to $(dirname $elastic_agent_config_path)"
update_step_progress "ea-install" "complete"
else
update_step_progress "ea-install" "danger" "Failed to install Elastic Agent, see script output for error."
@ -170,17 +174,14 @@ install_elastic_agent() {
wait_for_elastic_agent_status() {
local MAX_RETRIES=10
local i=0
echo -n "."
elastic-agent status > /dev/null 2>&1
local ELASTIC_AGENT_STATUS_EXIT_CODE="$?"
while [ "$ELASTIC_AGENT_STATUS_EXIT_CODE" -ne 0 ] && [ $i -le $MAX_RETRIES ]; do
sleep 1
echo -n "."
elastic-agent status > /dev/null 2>&1
ELASTIC_AGENT_STATUS_EXIT_CODE="$?"
((i++))
done
echo ""
if [ "$ELASTIC_AGENT_STATUS_EXIT_CODE" -ne 0 ]; then
update_step_progress "ea-status" "warning" "Unable to determine agent status"
@ -214,11 +215,11 @@ backup_elastic_agent_config() {
confirmation_reply="${confirmation_reply:-Y}"
if [[ "$confirmation_reply" =~ ^[Yy](es)?$ ]]; then
local backup_path="${elastic_agent_config_path%.yml}.$(date +%s).yml" # e.g. /opt/Elastic/Agent/elastic-agent.1712267614.yml
local backup_path="$(pwd)/$(basename "${elastic_agent_config_path%.yml}.$(date +%s).yml")" # e.g. /opt/Elastic/Agent/elastic-agent.1712267614.yml
cp $elastic_agent_config_path $backup_path
if [ "$?" -eq 0 ]; then
printf "\n\e[1;32m✓\e[0m \e[1m%s\e[0m\n" "Backup saved to $backup_path"
printf "\n\e[1;32m✓\e[0m %s\n" "Backup saved to $backup_path"
else
update_step_progress "ea-config" "warning" "Failed to backup existing configuration"
fail "Failed to backup existing config file - Try manually creating a backup or delete your existing config file before re-running this script"
@ -229,31 +230,56 @@ backup_elastic_agent_config() {
fi
}
download_elastic_agent_config() {
local decoded_ingest_api_key=$(echo "$ingest_api_key_encoded" | base64 -d)
local tmp_path="/tmp/elastic-agent-config-template.yml"
install_integrations() {
local install_integrations_api_body_string=""
update_step_progress "ea-config" "loading"
for item in "${selected_known_integrations_array[@]}"; do
install_integrations_api_body_string+="$item\tregistry\n"
done
for item in "${selected_unknown_log_file_pattern_array[@]}" "${custom_log_file_path_list_array[@]}"; do
local integration_name=$(generate_custom_integration_name "$item")
install_integrations_api_body_string+="$integration_name\tcustom\t$item\n"
done
curl --request POST \
-o $tmp_path \
-o $elastic_agent_tmp_config_path \
--url "$kibana_api_endpoint/internal/observability_onboarding/flow/$onboarding_flow_id/integrations/install" \
--header "Authorization: ApiKey $install_api_key_encoded" \
--header "Content-Type: text/tab-separated-values" \
--header "kbn-xsrf: true" \
--header "x-elastic-internal-origin: Kibana" \
--data "$(echo -e "$install_integrations_api_body_string")" \
--no-progress-meter
--no-progress-meter \
--fail
if [ "$?" -ne 0 ]; then
update_step_progress "ea-config" "warning" "Failed to install integrations."
fail "Failed to install integrations."
if [ "$?" -eq 0 ]; then
printf "\n\e[1;32m✓\e[0m %s\n" "Integrations installed"
else
update_step_progress "ea-config" "warning" "Failed to install integrations"
fail "Failed to install integrations"
fi
}
sed "s/'\${API_KEY}'/$decoded_ingest_api_key/g" $tmp_path > $elastic_agent_config_path
apply_elastic_agent_config() {
local decoded_ingest_api_key=$(echo "$ingest_api_key_encoded" | base64 -d)
sed "s/'\${API_KEY}'/$decoded_ingest_api_key/g" $elastic_agent_tmp_config_path > $elastic_agent_config_path
if [ "$?" -eq 0 ]; then
printf "\e[1;32m✓\e[0m %s\n" "Config written to $elastic_agent_config_path"
update_step_progress "ea-config" "complete"
else
update_step_progress "ea-config" "warning" "Failed to configure Elastic Agent"
fail "Failed to configure Elastic Agent"
fi
}
read_open_log_file_list() {
local exclude_patterns=(
"^\/Users\/.+?\/Library\/Application Support"
"^\/Users\/.+?\/Library\/Group Containers"
"^\/Users\/.+?\/Library\/Containers"
"^\/Users\/.+?\/Library\/Caches"
"^\/private"
# Excluding all patterns that correspond to known integrations
@ -269,7 +295,7 @@ read_open_log_file_list() {
"^\/var\/log\/secure"
)
local list=$(lsof -Fn | grep "\.log$" | awk '/^n/ {print substr($0, 2)}' | sort | uniq)
local list=$(lsof -Fn / | grep "^n.*\.log$" | cut -c2- | sort -u)
# Filtering by the exclude patterns
while IFS= read -r line; do
@ -385,12 +411,12 @@ function select_list() {
fi
done
printf "\n\e[1;36m?\e[0m \e[1m%s\e[0m \e[2m%s\e[0m" "Ingest all detected logs?" "[Y/n] (default: Yes): "
printf "\n\e[1;36m?\e[0m \e[1m%s\e[0m \e[2m%s\e[0m" "Continue installation with detected logs?" "[Y/n] (default: Yes): "
read confirmation_reply
confirmation_reply="${confirmation_reply:-Y}"
if [[ ! "$confirmation_reply" =~ ^[Yy](es)?$ ]]; then
printf "\n\e[1;36m?\e[0m \e[1m%s\e[0m \e[2m%s\e[0m" "Exclude logs by listing their index numbers" "(e.g. 1, 2, 3): "
printf "\n\e[1;36m?\e[0m \e[1m%s\e[0m \e[2m%s\e[0m\n" "Exclude logs by listing their index numbers" "(e.g. 1, 2, 3). Press Enter to skip."
read exclude_index_list_string
IFS=', ' read -r -a exclude_index_list_array <<< "$exclude_index_list_string"
@ -417,6 +443,33 @@ function select_list() {
fi
fi
done
if [[ -n "$excluded_options_string" ]]; then
echo -e "\nThese logs will not be ingested:"
echo -e "$excluded_options_string"
fi
printf "\e[1;36m?\e[0m \e[1m%s\e[0m \e[2m%s\e[0m\n" "List any additional logs you'd like to ingest" "(e.g. /path1/*.log, /path2/*.log). Press Enter to skip."
read custom_log_file_path_list_string
IFS=', ' read -r -a custom_log_file_path_list_array <<< "$custom_log_file_path_list_string"
echo -e "\nYou've selected these logs for ingestion:"
for item in "${selected_known_integrations_array[@]}"; do
printf "\e[32m•\e[0m %s\n" "$(known_integration_title "${item}")"
done
for item in "${selected_unknown_log_file_pattern_array[@]}" "${custom_log_file_path_list_array[@]}"; do
printf "\e[32m•\e[0m %s\n" "$item"
done
printf "\n\e[1;36m?\e[0m \e[1m%s\e[0m \e[2m%s\e[0m" "Continue installation with selected logs?" "[Y/n] (default: Yes): "
read confirmation_reply
confirmation_reply="${confirmation_reply:-Y}"
if [[ ! "$confirmation_reply" =~ ^[Yy](es)?$ ]]; then
echo -e "Rerun the script again to select different logs."
exit 1
fi
else
selected_known_integrations_array=("${known_integrations_options[@]}")
selected_unknown_log_file_pattern_array=("${unknown_logs_options[@]}")
@ -450,70 +503,26 @@ generate_custom_integration_name() {
echo "$name"
}
build_install_integrations_api_body_string() {
for item in "${selected_known_integrations_array[@]}"; do
install_integrations_api_body_string+="$item\tregistry\n"
done
for item in "${selected_unknown_log_file_pattern_array[@]}" "${custom_log_file_path_list_array[@]}"; do
local integration_name=$(generate_custom_integration_name "$item")
install_integrations_api_body_string+="$integration_name\tcustom\t$item\n"
done
}
echo "Looking for log files..."
printf "\e[1m%s\e[0m\n" "Looking for log files..."
update_step_progress "logs-detect" "loading"
detect_known_integrations
read_open_log_file_list
build_unknown_log_file_patterns
update_step_progress "logs-detect" "complete"
echo -e "\nWe found these logs on your system:"
select_list
if [[ -n "$excluded_options_string" ]]; then
echo -e "\nThese logs will not be ingested:"
echo -e "$excluded_options_string"
fi
printf "\n\e[1;36m?\e[0m \e[1m%s\e[0m \e[2m%s\e[0m\n" "Add paths to any custom logs we've missed" "(e.g. /path1/*.log, /path2/*.log). Press Enter to skip."
read custom_log_file_path_list_string
IFS=', ' read -r -a custom_log_file_path_list_array <<< "$custom_log_file_path_list_string"
echo -e "\nYou've selected these logs to ingest:"
for item in "${selected_known_integrations_array[@]}"; do
printf "• %s\n" "$(known_integration_title "${item}")"
done
for item in "${selected_unknown_log_file_pattern_array[@]}" "${custom_log_file_path_list_array[@]}"; do
printf "• %s\n" "$item"
done
printf "\n\e[1;36m?\e[0m \e[1m%s\e[0m \e[2m%s\e[0m" "Continue installation with selected logs?" "[Y/n] (default: Yes): "
read confirmation_reply
confirmation_reply="${confirmation_reply:-Y}"
if [[ ! "$confirmation_reply" =~ ^[Yy](es)?$ ]]; then
echo -e "Rerun the script again to select different logs."
exit 1
fi
build_install_integrations_api_body_string
backup_elastic_agent_config
echo -e "\nDownloading Elastic Agent...\n"
printf "\n\e[1m%s\e[0m\n" "Installing Elastic Agent..."
install_integrations
download_elastic_agent
extract_elastic_agent
echo -e "\nInstalling Elastic Agent...\n"
install_elastic_agent
apply_elastic_agent_config
printf "\n\e[1m%s\e[0m\n" "Waiting for healthy status..."
wait_for_elastic_agent_status
ensure_elastic_agent_healthy
echo -e "\nInstalling integrations...\n"
download_elastic_agent_config
update_step_progress "ea-config" "complete"
printf "\n\e[32m%s\e[0m\n" "🎉 Elastic Agent is configured and running. You can now go back to Kibana and check for incoming logs."

View file

@ -0,0 +1,128 @@
<svg width="162" height="117" viewBox="0 0 162 117" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6_66533)">
<rect width="162" height="117" rx="3.6" fill="#F7F8FC"/>
<g filter="url(#filter0_dddd_6_66533)">
<rect width="135" height="99" transform="translate(27 18)" fill="white"/>
<rect x="37.6387" y="105.891" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="52.9395" y="105.891" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="68.2383" y="105.891" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="83.5391" y="105.891" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="98.8379" y="105.891" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="114.139" y="105.891" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="129.439" y="105.891" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="144.738" y="105.891" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="37.6387" y="62.6907" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="52.9395" y="62.6907" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="68.2383" y="62.6907" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="83.5391" y="62.6907" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="98.8379" y="62.6907" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="114.139" y="62.6907" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="129.439" y="62.6907" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="144.738" y="62.6907" width="6.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<path d="M38.25 48.0639L55.243 33.6119L63.8969 40.9717L70.9773 34.95L86.3969 48.0639L113.302 25.1815L134.386 43.1127L140.995 37.4925L150.75 45.7891" stroke="#F1F4FA" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M38.25 40.8918L49.677 50.6101L59.4945 42.2606L67.8635 49.3782L85.8892 34.048L109.87 54.4427L117.112 48.2832L127.252 56.9065C135.138 50.2907 150.878 37.0319 150.749 36.9224" stroke="#F1F4FA" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M150.75 78.5426L129.091 100.201L115.847 86.9567L109.458 93.3452L94.6558 78.5426L75.3345 97.8639L69.4134 91.9429L63.4924 97.8639L46.8199 81.1915L38.25 89.7614" stroke="#F1F4FA" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M150.75 74.079L131.455 93.3736L124.92 86.8383L116.829 94.9296L104.225 82.3259L93.1774 93.3736L72.3268 72.5229L49.9201 94.9296L46.0301 91.0395L38.25 98.8196" stroke="#F1F4FA" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
<mask id="mask0_6_66533" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="37" y="18" width="115" height="99">
<rect x="37.1875" y="18" width="114.75" height="99" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_6_66533)">
<path d="M38.25 48.0639L55.243 33.6119L63.8969 40.9717L70.9773 34.95L86.3969 48.0639L113.302 25.1815L134.386 43.1127L140.995 37.4925L150.75 45.7891" stroke="#6092C0" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M150.75 78.5426L129.091 100.201L115.847 86.9567L109.458 93.3452L94.6558 78.5426L75.3345 97.8639L69.4134 91.9429L63.4924 97.8639L46.8199 81.1915L38.25 89.7614" stroke="#6092C0" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<mask id="mask1_6_66533" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="27" y="18" width="125" height="99">
<rect x="27" y="18" width="124.489" height="99" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask1_6_66533)">
<path d="M38.25 40.8918L49.677 50.6101L59.4945 42.2606L67.8635 49.3782L85.8892 34.048L109.87 54.4427L117.112 48.2832L127.252 56.9065C135.138 50.2907 150.878 37.0319 150.749 36.9224" stroke="#D36086" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M150.75 74.0781L131.455 93.3727L124.92 86.8374L116.829 94.9287L104.225 82.325L93.1774 93.3727L72.3268 72.5221L49.9201 94.9287L46.0301 91.0387L38.25 98.8188" stroke="#D36086" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</g>
<rect x="3.15039" y="27" width="13.5" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="31.05" width="5.4" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="35.1" width="14.85" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="39.15" width="13.5" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="43.2" width="15.75" height="1.8" rx="0.9" fill="#98A2B3"/>
<rect x="3.15039" y="47.25" width="10.8" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="51.3" width="13.5" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="55.35" width="7.2" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="59.4" width="12.15" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="21.15" width="16.65" height="2.7" rx="1.35" fill="#98A2B3"/>
<g filter="url(#filter1_ddd_6_66533)">
<rect width="162" height="9" transform="translate(0 9)" fill="white"/>
<path d="M26.0996 11.25H35.0996C35.5967 11.25 35.9996 11.6529 35.9996 12.15V14.85C35.9996 15.3471 35.5967 15.75 35.0996 15.75H26.0996V11.25Z" fill="#69707D" fill-opacity="0.2"/>
<path d="M15.75 12.15C15.75 11.6529 16.1529 11.25 16.65 11.25H25.65V15.75H16.65C16.1529 15.75 15.75 15.3471 15.75 14.85V12.15Z" fill="#006DE4" fill-opacity="0.2"/>
<rect x="9" y="11.25" width="4.5" height="4.5" rx="0.9" fill="#00BFB3"/>
<rect x="2.25" y="11.25" width="4.5" height="4.5" rx="0.9" fill="#D3DAE6"/>
</g>
<rect width="162" height="9" fill="#26282F"/>
<circle cx="157.501" cy="4.50005" r="2.7" fill="#0077CC"/>
<circle cx="150.3" cy="4.50005" r="2.7" fill="#69707D"/>
<rect x="62.0996" y="1.80005" width="38.25" height="5.4" rx="1.35" fill="#69707D"/>
<rect x="9.45117" y="3.15002" width="13.05" height="2.7" rx="1.35" fill="#69707D"/>
<g clip-path="url(#clip1_6_66533)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.00841 4.01557C7.13416 4.19573 7.20134 4.41027 7.20079 4.62999C7.1997 4.85059 7.13129 5.0656 7.0047 5.24626C6.87836 5.42659 6.6998 5.56387 6.49305 5.63962C6.55327 5.80507 6.55834 5.98557 6.50748 6.15414C6.45662 6.32271 6.35258 6.47029 6.2109 6.57483C6.06931 6.67888 5.89766 6.73396 5.72197 6.7317C5.54627 6.72945 5.37609 6.66999 5.23721 6.56234C5.04992 6.82498 4.78399 7.02129 4.47784 7.12293C4.17197 7.22442 3.84172 7.22578 3.53503 7.12681C3.22807 7.02759 2.9606 6.83334 2.77127 6.57213C2.58151 6.31046 2.47948 5.99542 2.47984 5.67218C2.4798 5.57453 2.48883 5.47708 2.50684 5.38109C2.29947 5.30677 2.12028 5.16992 1.99401 4.98942C1.86746 4.80852 1.79997 4.5929 1.80079 4.37214C1.80193 4.15152 1.8704 3.93651 1.99704 3.75586C2.12355 3.57552 2.3023 3.43829 2.5092 3.36267C2.44812 3.19714 2.44238 3.01626 2.49282 2.84718C2.54327 2.67809 2.64719 2.52993 2.78899 2.42493C2.93066 2.32037 3.1026 2.26495 3.27866 2.26708C3.45472 2.26921 3.62527 2.32879 3.76436 2.43674C3.96714 2.15359 4.26072 1.94838 4.5963 1.85523C4.93162 1.76233 5.28866 1.78728 5.60779 1.92594C5.92731 2.06487 6.18972 2.30888 6.35147 2.61747C6.51354 2.92651 6.56551 3.28154 6.49879 3.62407C6.70499 3.69885 6.88301 3.8356 7.00841 4.01557ZM3.92366 4.1266L5.10525 4.66846L6.29764 3.62002C6.31491 3.53351 6.3235 3.44549 6.32329 3.35727C6.32341 3.07175 6.23236 2.79365 6.06341 2.56347C5.89487 2.33371 5.65711 2.16403 5.38504 2.07933C5.11323 1.99485 4.82147 1.99994 4.55276 2.09384C4.28368 2.18791 4.05184 2.36576 3.89126 2.60127L3.69315 3.63369L3.92383 4.12644L3.92366 4.1266ZM2.70073 5.38227C2.66339 5.56605 2.66492 5.75562 2.70523 5.93877C2.74554 6.12193 2.82373 6.29462 2.93479 6.44574C3.10426 6.67621 3.34323 6.84621 3.61654 6.93072C3.88952 7.01503 4.18238 7.00929 4.45185 6.91435C4.72173 6.81922 4.95395 6.64 5.11436 6.40304L5.31113 5.37333L5.04872 4.86961L3.86241 4.32775L2.70056 5.3821L2.70073 5.38227ZM3.50314 3.52197L2.69314 3.3301L2.69348 3.32977C2.64661 3.19965 2.64283 3.05792 2.6827 2.92549C2.72257 2.79306 2.80397 2.67697 2.91488 2.59435C3.02579 2.51215 3.16036 2.4681 3.29842 2.4688C3.43647 2.4695 3.57058 2.51491 3.68066 2.59824L3.50314 3.52197ZM2.62294 3.52366C2.44766 3.58172 2.29467 3.69267 2.18503 3.84125C2.07512 3.99007 2.01383 4.1692 2.00954 4.35417C2.00525 4.53913 2.05817 4.72091 2.16107 4.87467C2.26384 5.02823 2.41149 5.14636 2.58379 5.21217L3.71998 4.18263L3.51158 3.73544L2.62294 3.52366ZM5.7087 6.53551C5.56905 6.53527 5.43342 6.48868 5.32311 6.40304L5.4981 5.48268L6.3081 5.67252C6.34348 5.76944 6.35504 5.87345 6.34179 5.97577C6.32855 6.07809 6.29089 6.17573 6.232 6.26045C6.17341 6.34509 6.09523 6.41431 6.00411 6.4622C5.91299 6.5101 5.81164 6.53525 5.7087 6.53551ZM5.48764 5.27005L6.37864 5.47897C6.55661 5.41875 6.71131 5.3044 6.8211 5.15193C6.93113 4.99921 6.99088 4.81602 6.99204 4.62779C6.992 4.4464 6.93692 4.26929 6.83409 4.11985C6.73144 3.97061 6.58575 3.85618 6.41644 3.7918L5.25139 4.81612L5.48764 5.27005Z" fill="white"/>
<path d="M3.92387 4.12664L5.10546 4.6685L6.29785 3.62006C6.31512 3.53355 6.32371 3.44553 6.3235 3.35731C6.32362 3.07179 6.23257 2.79368 6.06362 2.56351C5.89507 2.33375 5.65732 2.16407 5.38525 2.07937C5.11343 1.99489 4.82167 1.99998 4.55297 2.09388C4.28389 2.18795 4.05205 2.3658 3.89147 2.60131L3.69336 3.63372L3.92404 4.12647L3.92387 4.12664Z" fill="#FEC514"/>
<path d="M2.70075 5.38228C2.6634 5.56606 2.66493 5.75562 2.70524 5.93878C2.74555 6.12193 2.82375 6.29462 2.9348 6.44574C3.10427 6.67622 3.34324 6.84622 3.61655 6.93073C3.88953 7.01503 4.18239 7.0093 4.45186 6.91436C4.72174 6.81923 4.95396 6.64 5.11438 6.40305L5.31114 5.37333L5.04873 4.86962L3.86242 4.32776L2.70058 5.38211L2.70075 5.38228Z" fill="#00BFB3"/>
<path d="M2.6921 3.33006L3.5021 3.52193L3.67963 2.59819C3.56955 2.51487 3.43543 2.46946 3.29738 2.46876C3.15932 2.46806 3.02476 2.51211 2.91384 2.59431C2.80293 2.67693 2.72153 2.79302 2.68166 2.92545C2.6418 3.05787 2.64557 3.19961 2.69244 3.32972" fill="#F04E98"/>
<path d="M2.62341 3.52368C2.44812 3.58174 2.29514 3.6927 2.1855 3.84127C2.07558 3.99009 2.0143 4.16923 2.01001 4.35419C2.00572 4.53916 2.05864 4.72093 2.16154 4.87469C2.26431 5.02826 2.41196 5.14638 2.58426 5.21219L3.72045 4.18265L3.51204 3.73546L2.62341 3.52368V3.52368Z" fill="#1BA9F5"/>
<path d="M5.32422 6.40303C5.43454 6.48866 5.57016 6.53526 5.70981 6.5355C5.81275 6.53524 5.9141 6.51009 6.00522 6.46219C6.09634 6.4143 6.17452 6.34508 6.23311 6.26043C6.292 6.17572 6.32966 6.07808 6.3429 5.97576C6.35615 5.87344 6.34459 5.76943 6.30921 5.67251L5.49921 5.48267L5.32422 6.40303Z" fill="#93C90E"/>
<path d="M5.4882 5.27012L6.3792 5.47903C6.55718 5.41881 6.71188 5.30447 6.82167 5.152C6.9317 4.99928 6.99145 4.81608 6.99261 4.62786C6.99256 4.44646 6.93749 4.26935 6.83466 4.11992C6.732 3.97068 6.58632 3.85625 6.417 3.79187L5.25195 4.81618L5.4882 5.27012V5.27012Z" fill="#0077CC"/>
</g>
</g>
<defs>
<filter id="filter0_dddd_6_66533" x="20.7" y="15.615" width="147.6" height="114.435" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="0.45" operator="erode" in="SourceAlpha" result="effect1_dropShadow_6_66533"/>
<feOffset dy="6.75"/>
<feGaussianBlur stdDeviation="3.375"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_66533"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="0.45" operator="erode" in="SourceAlpha" result="effect2_dropShadow_6_66533"/>
<feOffset dy="2.565"/>
<feGaussianBlur stdDeviation="2.7"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_6_66533" result="effect2_dropShadow_6_66533"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="0.45" operator="erode" in="SourceAlpha" result="effect3_dropShadow_6_66533"/>
<feOffset dy="1.17"/>
<feGaussianBlur stdDeviation="1.8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="effect2_dropShadow_6_66533" result="effect3_dropShadow_6_66533"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="0.45" operator="erode" in="SourceAlpha" result="effect4_dropShadow_6_66533"/>
<feOffset dy="0.405"/>
<feGaussianBlur stdDeviation="0.9"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
<feBlend mode="normal" in2="effect3_dropShadow_6_66533" result="effect4_dropShadow_6_66533"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect4_dropShadow_6_66533" result="shape"/>
</filter>
<filter id="filter1_ddd_6_66533" x="-4.5" y="6.525" width="171" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2.025"/>
<feGaussianBlur stdDeviation="2.25"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_66533"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="0.855"/>
<feGaussianBlur stdDeviation="0.9"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_6_66533" result="effect2_dropShadow_6_66533"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="0.315"/>
<feGaussianBlur stdDeviation="0.315"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.07 0"/>
<feBlend mode="normal" in2="effect2_dropShadow_6_66533" result="effect3_dropShadow_6_66533"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_6_66533" result="shape"/>
</filter>
<clipPath id="clip0_6_66533">
<rect width="162" height="117" rx="3.6" fill="white"/>
</clipPath>
<clipPath id="clip1_6_66533">
<rect width="5.4" height="5.4" fill="white" transform="translate(1.80078 1.80005)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,116 @@
<svg width="162" height="117" viewBox="0 0 162 117" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6_66599)">
<rect width="162" height="117" rx="3.6" fill="#F7F8FC"/>
<g clip-path="url(#clip1_6_66599)" filter="url(#filter0_dddd_6_66599)">
<rect width="135" height="99" transform="translate(27 18)" fill="white"/>
<rect x="37.7988" y="27" width="113.4" height="5.4" rx="1.35" fill="#F1F4FA"/>
<rect x="37.7988" y="39.6" width="113.4" height="5.4" rx="1.35" fill="#F1F4FA"/>
<rect x="37.7988" y="52.2" width="113.4" height="5.4" rx="1.35" fill="#F1F4FA"/>
<rect x="37.7988" y="64.8" width="113.4" height="5.4" rx="1.35" fill="#F1F4FA"/>
<rect x="37.7988" y="77.4" width="113.4" height="5.4" rx="1.35" fill="#F1F4FA"/>
<rect x="37.7988" y="90" width="113.4" height="5.4" rx="1.35" fill="#F1F4FA"/>
<rect x="37.7988" y="102.6" width="113.4" height="5.4" rx="1.35" fill="#F1F4FA"/>
<mask id="mask0_6_66599" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="27" y="18" width="135" height="99">
<rect x="27" y="18" width="135" height="99" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_6_66599)">
<rect x="37.7988" y="27" width="113.4" height="5.4" rx="1.35" fill="#6092C0"/>
<rect x="37.7988" y="39.6" width="99.675" height="5.4" rx="1.35" fill="#6092C0"/>
<rect x="63.8984" y="52.2" width="81.675" height="5.4" rx="1.35" fill="#54B399"/>
<rect x="80.7734" y="64.8" width="49.05" height="5.4" rx="1.35" fill="#54B399"/>
<rect x="93.373" y="77.4" width="31.5" height="5.4" rx="1.35" fill="#9170B8"/>
<rect x="99.7871" y="90" width="18.675" height="5.4" rx="1.35" fill="#9170B8"/>
<rect x="105.299" y="102.6" width="8.1" height="5.4" rx="1.35" fill="#D36086"/>
</g>
</g>
<rect x="3.15039" y="27" width="13.5" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="31.05" width="5.4" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="35.1" width="14.85" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="39.15" width="13.5" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="43.2" width="15.75" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="47.25" width="10.8" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="51.3" width="13.5" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="55.35" width="7.2" height="1.8" rx="0.9" fill="#98A2B3"/>
<rect x="3.15039" y="59.4" width="12.15" height="1.8" rx="0.9" fill="#D3DAE6"/>
<rect x="3.15039" y="21.15" width="16.65" height="2.7" rx="1.35" fill="#98A2B3"/>
<g filter="url(#filter1_ddd_6_66599)">
<rect width="162" height="9" transform="translate(0 9)" fill="white"/>
<path d="M26.0996 11.25H35.0996C35.5967 11.25 35.9996 11.6529 35.9996 12.15V14.85C35.9996 15.3471 35.5967 15.75 35.0996 15.75H26.0996V11.25Z" fill="#69707D" fill-opacity="0.2"/>
<path d="M15.75 12.15C15.75 11.6529 16.1529 11.25 16.65 11.25H25.65V15.75H16.65C16.1529 15.75 15.75 15.3471 15.75 14.85V12.15Z" fill="#006DE4" fill-opacity="0.2"/>
<rect x="9" y="11.25" width="4.5" height="4.5" rx="0.9" fill="#00BFB3"/>
<rect x="2.25" y="11.25" width="4.5" height="4.5" rx="0.9" fill="#D3DAE6"/>
</g>
<rect width="162" height="9" fill="#26282F"/>
<circle cx="157.501" cy="4.50005" r="2.7" fill="#0077CC"/>
<circle cx="150.3" cy="4.50005" r="2.7" fill="#69707D"/>
<rect x="62.0996" y="1.80005" width="38.25" height="5.4" rx="1.35" fill="#69707D"/>
<rect x="9.44922" y="3.15015" width="13.05" height="2.7" rx="1.35" fill="#69707D"/>
<g clip-path="url(#clip2_6_66599)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.00646 4.01569C7.13221 4.19586 7.19938 4.4104 7.19883 4.63011C7.19775 4.85071 7.12934 5.06572 7.00275 5.24638C6.87641 5.42671 6.69784 5.564 6.4911 5.63974C6.55132 5.80519 6.55638 5.98569 6.50553 6.15426C6.45467 6.32283 6.35063 6.47041 6.20895 6.57495C6.06736 6.67901 5.89571 6.73408 5.72001 6.73183C5.54431 6.72957 5.37414 6.67012 5.23526 6.56246C5.04797 6.8251 4.78204 7.02142 4.47589 7.12305C4.17002 7.22454 3.83977 7.2259 3.53308 7.12693C3.22611 7.02771 2.95864 6.83346 2.76932 6.57225C2.57955 6.31058 2.47753 5.99554 2.47789 5.67231C2.47784 5.57465 2.48688 5.4772 2.50489 5.38121C2.29752 5.30689 2.11833 5.17004 1.99205 4.98954C1.86551 4.80864 1.79802 4.59303 1.79884 4.37226C1.79998 4.15164 1.86845 3.93663 1.99509 3.75598C2.1216 3.57564 2.30034 3.43841 2.50725 3.36279C2.44617 3.19726 2.44042 3.01638 2.49087 2.8473C2.54132 2.67822 2.64524 2.53006 2.78704 2.42505C2.92871 2.3205 3.10065 2.26507 3.27671 2.2672C3.45277 2.26934 3.62331 2.32891 3.76241 2.43686C3.96519 2.15372 4.25877 1.94851 4.59435 1.85535C4.92966 1.76245 5.28671 1.78741 5.60584 1.92606C5.92536 2.06499 6.18777 2.309 6.34952 2.61759C6.51159 2.92663 6.56355 3.28167 6.49684 3.62419C6.70304 3.69897 6.88106 3.83573 7.00646 4.01569ZM3.92171 4.12673L5.1033 4.66858L6.29569 3.62014C6.31296 3.53363 6.32155 3.44561 6.32134 3.35739C6.32146 3.07187 6.23041 2.79377 6.06146 2.56359C5.89291 2.33384 5.65516 2.16415 5.38309 2.07945C5.11127 1.99498 4.81951 2.00006 4.55081 2.09396C4.28173 2.18804 4.04989 2.36588 3.88931 2.60139L3.6912 3.63381L3.92188 4.12656L3.92171 4.12673ZM2.69878 5.38239C2.66143 5.56618 2.66296 5.75574 2.70327 5.93889C2.74358 6.12205 2.82178 6.29474 2.93284 6.44586C3.10231 6.67633 3.34128 6.84633 3.61459 6.93084C3.88757 7.01515 4.18043 7.00941 4.4499 6.91448C4.71977 6.81935 4.952 6.64012 5.11241 6.40316L5.30917 5.37345L5.04677 4.86973L3.86045 4.32788L2.69861 5.38223L2.69878 5.38239ZM3.50118 3.52209L2.69119 3.33023L2.69152 3.32989C2.64466 3.19977 2.64088 3.05804 2.68075 2.92561C2.72061 2.79319 2.80201 2.67709 2.91292 2.59448C3.02384 2.51227 3.15841 2.46822 3.29646 2.46892C3.43452 2.46962 3.56863 2.51504 3.67871 2.59836L3.50118 3.52209ZM2.62099 3.52378C2.4457 3.58185 2.29272 3.6928 2.18308 3.84137C2.07316 3.9902 2.01187 4.16933 2.00759 4.35429C2.0033 4.53926 2.05622 4.72103 2.15912 4.87479C2.26189 5.02836 2.40954 5.14648 2.58184 5.21229L3.71803 4.18275L3.50962 3.73556L2.62099 3.52378ZM5.70675 6.53563C5.56709 6.53539 5.43147 6.4888 5.32115 6.40316L5.49615 5.4828L6.30615 5.67264C6.34153 5.76956 6.35308 5.87357 6.33984 5.97589C6.32659 6.07822 6.28894 6.17585 6.23004 6.26057C6.17146 6.34521 6.09327 6.41443 6.00215 6.46233C5.91104 6.51022 5.80969 6.53537 5.70675 6.53563ZM5.48568 5.27018L6.37668 5.47909C6.55466 5.41887 6.70936 5.30452 6.81915 5.15205C6.92918 4.99933 6.98893 4.81614 6.99009 4.62791C6.99005 4.44652 6.93497 4.26941 6.83214 4.11998C6.72948 3.97073 6.5838 3.85631 6.41448 3.79193L5.24943 4.81624L5.48568 5.27018Z" fill="white"/>
<path d="M3.92192 4.12677L5.10351 4.66862L6.29589 3.62018C6.31317 3.53367 6.32176 3.44565 6.32154 3.35743C6.32166 3.07191 6.23062 2.79381 6.06167 2.56363C5.89312 2.33388 5.65536 2.16419 5.38329 2.07949C5.11148 1.99501 4.81972 2.0001 4.55102 2.094C4.28194 2.18808 4.0501 2.36592 3.88952 2.60143L3.69141 3.63385L3.92209 4.1266L3.92192 4.12677Z" fill="#FEC514"/>
<path d="M2.69879 5.3824C2.66145 5.56618 2.66298 5.75574 2.70329 5.9389C2.7436 6.12205 2.82179 6.29474 2.93285 6.44586C3.10232 6.67634 3.34129 6.84634 3.6146 6.93085C3.88758 7.01516 4.18044 7.00942 4.44991 6.91448C4.71979 6.81935 4.95201 6.64013 5.11242 6.40317L5.30919 5.37346L5.04678 4.86974L3.86047 4.32788L2.69862 5.38223L2.69879 5.3824Z" fill="#00BFB3"/>
<path d="M2.69015 3.33018L3.50015 3.52205L3.67767 2.59831C3.56759 2.51499 3.43348 2.46958 3.29543 2.46888C3.15737 2.46818 3.0228 2.51223 2.91189 2.59443C2.80098 2.67705 2.71958 2.79314 2.67971 2.92557C2.63984 3.05799 2.64362 3.19973 2.69049 3.32984" fill="#F04E98"/>
<path d="M2.62145 3.5238C2.44617 3.58187 2.29319 3.69282 2.18355 3.84139C2.07363 3.99022 2.01234 4.16935 2.00805 4.35431C2.00377 4.53928 2.05669 4.72106 2.15959 4.87482C2.26235 5.02838 2.41001 5.1465 2.5823 5.21232L3.7185 4.18277L3.51009 3.73558L2.62145 3.5238V3.5238Z" fill="#1BA9F5"/>
<path d="M5.32227 6.40315C5.43258 6.48878 5.56821 6.53538 5.70786 6.53562C5.8108 6.53536 5.91215 6.51021 6.00327 6.46231C6.09438 6.41442 6.17257 6.3452 6.23115 6.26056C6.29005 6.17584 6.3277 6.0782 6.34095 5.97588C6.35419 5.87356 6.34264 5.76955 6.30726 5.67263L5.49726 5.48279L5.32227 6.40315Z" fill="#93C90E"/>
<path d="M5.48625 5.27024L6.37725 5.47915C6.55522 5.41894 6.70993 5.30459 6.81971 5.15212C6.92975 4.9994 6.98949 4.81621 6.99066 4.62798C6.99061 4.44659 6.93554 4.26947 6.83271 4.12004C6.73005 3.9708 6.58436 3.85637 6.41505 3.79199L5.25 4.8163L5.48625 5.27024V5.27024Z" fill="#0077CC"/>
</g>
</g>
<defs>
<filter id="filter0_dddd_6_66599" x="20.7" y="15.615" width="147.6" height="114.435" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="0.45" operator="erode" in="SourceAlpha" result="effect1_dropShadow_6_66599"/>
<feOffset dy="6.75"/>
<feGaussianBlur stdDeviation="3.375"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_66599"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="0.45" operator="erode" in="SourceAlpha" result="effect2_dropShadow_6_66599"/>
<feOffset dy="2.565"/>
<feGaussianBlur stdDeviation="2.7"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_6_66599" result="effect2_dropShadow_6_66599"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="0.45" operator="erode" in="SourceAlpha" result="effect3_dropShadow_6_66599"/>
<feOffset dy="1.17"/>
<feGaussianBlur stdDeviation="1.8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="effect2_dropShadow_6_66599" result="effect3_dropShadow_6_66599"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="0.45" operator="erode" in="SourceAlpha" result="effect4_dropShadow_6_66599"/>
<feOffset dy="0.405"/>
<feGaussianBlur stdDeviation="0.9"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
<feBlend mode="normal" in2="effect3_dropShadow_6_66599" result="effect4_dropShadow_6_66599"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect4_dropShadow_6_66599" result="shape"/>
</filter>
<filter id="filter1_ddd_6_66599" x="-4.5" y="6.525" width="171" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2.025"/>
<feGaussianBlur stdDeviation="2.25"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_66599"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="0.855"/>
<feGaussianBlur stdDeviation="0.9"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_6_66599" result="effect2_dropShadow_6_66599"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="0.315"/>
<feGaussianBlur stdDeviation="0.315"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.07 0"/>
<feBlend mode="normal" in2="effect2_dropShadow_6_66599" result="effect3_dropShadow_6_66599"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_6_66599" result="shape"/>
</filter>
<clipPath id="clip0_6_66599">
<rect width="162" height="117" rx="3.6" fill="white"/>
</clipPath>
<clipPath id="clip1_6_66599">
<rect width="135" height="99" fill="white" transform="translate(27 18)"/>
</clipPath>
<clipPath id="clip2_6_66599">
<rect width="5.4" height="5.4" fill="white" transform="translate(1.79883 1.80017)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -10,7 +10,10 @@ import {
ObservabilityPublicStart,
} from '@kbn/observability-plugin/public';
import {
HttpStart,
ObservabilitySharedPluginSetup,
ObservabilitySharedPluginStart,
} from '@kbn/observability-shared-plugin/public';
import {
AppMountParameters,
CoreSetup,
CoreStart,
@ -20,7 +23,12 @@ import {
} from '@kbn/core/public';
import type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/common';
import { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public';
import { SharePluginSetup } from '@kbn/share-plugin/public';
import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public';
import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
import { DiscoverSetup, DiscoverStart } from '@kbn/discover-plugin/public';
import { FleetSetup, FleetStart } from '@kbn/fleet-plugin/public';
import { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
import { UsageCollectionSetup, UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import type { ObservabilityOnboardingConfig } from '../server';
import { PLUGIN_ID } from '../common';
import { ObservabilityOnboardingLocatorDefinition } from './locators/onboarding_locator/locator_definition';
@ -34,23 +42,30 @@ export type ObservabilityOnboardingPluginStart = void;
export interface ObservabilityOnboardingPluginSetupDeps {
data: DataPublicPluginSetup;
observability: ObservabilityPublicSetup;
observabilityShared: ObservabilitySharedPluginSetup;
discover: DiscoverSetup;
share: SharePluginSetup;
fleet: FleetSetup;
security: SecurityPluginSetup;
cloud?: CloudSetup;
usageCollection?: UsageCollectionSetup;
}
export interface ObservabilityOnboardingPluginStartDeps {
cloudExperiments?: CloudExperimentsPluginStart;
http: HttpStart;
data: DataPublicPluginStart;
observability: ObservabilityPublicStart;
observabilityShared: ObservabilitySharedPluginStart;
discover: DiscoverStart;
share: SharePluginStart;
fleet: FleetStart;
security: SecurityPluginStart;
cloud?: CloudStart;
usageCollection?: UsageCollectionStart;
cloudExperiments?: CloudExperimentsPluginStart;
}
export interface ObservabilityOnboardingPluginContextValue {
core: CoreStart;
plugins: ObservabilityOnboardingPluginSetupDeps;
data: DataPublicPluginStart;
observability: ObservabilityPublicStart;
config: ConfigSchema;
}
export type ObservabilityOnboardingContextValue = CoreStart &
ObservabilityOnboardingPluginStartDeps & { config: ConfigSchema };
export class ObservabilityOnboardingPlugin
implements Plugin<ObservabilityOnboardingPluginSetup, ObservabilityOnboardingPluginStart>

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { FleetStartContract } from '@kbn/fleet-plugin/server';
export function getAgentVersion(fleetStart: FleetStartContract, kibanaVersion: string) {
// If undefined, we will follow fleet's strategy to select latest available version:
// for serverless we will use the latest published version, for statefull we will use
// current Kibana version. If false, irrespective of fleet flags and logic, we are
// explicitly deciding to not append the current version.
const includeCurrentVersion = kibanaVersion.endsWith('-SNAPSHOT') ? false : undefined;
const agentClient = fleetStart.agentService.asInternalUser;
return agentClient.getLatestAgentAvailableVersion(includeCurrentVersion);
}

View file

@ -5,10 +5,19 @@
* 2.0.
*/
import { CoreStart } from '@kbn/core/server';
import { CoreSetup } from '@kbn/core/server';
import { CloudSetup } from '@kbn/cloud-plugin/server';
import { EsLegacyConfigService } from '../services/es_legacy_config_service';
export function getFallbackKibanaUrl({ http }: CoreStart) {
export function getKibanaUrl(coreSetup: CoreSetup, cloudSetup?: CloudSetup) {
return (
coreSetup.http.basePath.publicBaseUrl ?? // priority given to server.publicBaseUrl
cloudSetup?.kibanaUrl ?? // then cloud id
getFallbackKibanaUrl(coreSetup) // falls back to local network binding
);
}
export function getFallbackKibanaUrl({ http }: CoreSetup) {
const basePath = http.basePath;
const { protocol, hostname, port } = http.getServerInfo();
return `${protocol}://${hostname}:${port}${basePath

View file

@ -7,52 +7,24 @@
import { ElasticsearchClient } from '@kbn/core/server';
import { termQuery } from '@kbn/observability-plugin/server';
import type { estypes } from '@elastic/elasticsearch';
import { AGENT_ID } from '../../../common/es_fields';
import {
LogFilesState,
ObservabilityOnboardingType,
SystemLogsState,
} from '../../saved_objects/observability_onboarding_status';
import { ElasticAgentStepPayload } from '../types';
export async function getHasLogs({
type,
state,
esClient,
payload,
}: {
type: ObservabilityOnboardingType;
state?: LogFilesState | SystemLogsState;
esClient: ElasticsearchClient;
payload?: ElasticAgentStepPayload;
}) {
if (!state) {
return false;
}
export async function getHasLogs(esClient: ElasticsearchClient, agentId: string) {
try {
const { namespace } = state;
const index =
type === 'logFiles'
? `logs-${(state as LogFilesState).datasetName}-${namespace}`
: [`logs-system.syslog-${namespace}`, `logs-system.auth-${namespace}`];
const agentId = payload?.agentId;
const { hits } = await esClient.search({
index,
const result = await esClient.search({
index: ['logs-*', 'metrics-*'],
ignore_unavailable: true,
size: 0,
terminate_after: 1,
body: {
query: {
bool: {
filter: [...termQuery(AGENT_ID, agentId)],
},
query: {
bool: {
filter: termQuery(AGENT_ID, agentId),
},
},
});
const total = hits.total as { value: number };
return total.value > 0;
const { value } = result.hits.total as estypes.SearchTotalHits;
return value > 0;
} catch (error) {
if (error.statusCode === 404) {
return false;

View file

@ -12,15 +12,20 @@ import {
FleetUnauthorizedError,
type PackageClient,
} from '@kbn/fleet-plugin/server';
import type { TemplateAgentPolicyInput } from '@kbn/fleet-plugin/common';
import { dump } from 'js-yaml';
import { PackageDataStreamTypes } from '@kbn/fleet-plugin/common/types';
import { getObservabilityOnboardingFlow, saveObservabilityOnboardingFlow } from '../../lib/state';
import type { SavedObservabilityOnboardingFlow } from '../../saved_objects/observability_onboarding_status';
import { ObservabilityOnboardingFlow } from '../../saved_objects/observability_onboarding_status';
import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route';
import { getHasLogs } from './get_has_logs';
import { getKibanaUrl } from '../../lib/get_fallback_urls';
import { hasLogMonitoringPrivileges } from '../logs/api_key/has_log_monitoring_privileges';
import { createShipperApiKey } from '../logs/api_key/create_shipper_api_key';
import { createInstallApiKey } from '../logs/api_key/create_install_api_key';
import { getAgentVersion } from '../../lib/get_agent_version';
import { getFallbackESUrl } from '../../lib/get_fallback_urls';
import { ElasticAgentStepPayload, Integration, StepProgressPayloadRT } from '../types';
import { ElasticAgentStepPayload, InstalledIntegration, StepProgressPayloadRT } from '../types';
const updateOnboardingFlowRoute = createObservabilityOnboardingServerRoute({
endpoint: 'PUT /internal/observability_onboarding/flow/{onboardingId}',
@ -129,9 +134,7 @@ const getProgressRoute = createObservabilityOnboardingServerRoute({
onboardingId: t.string,
}),
}),
async handler(resources): Promise<{
progress: Record<string, { status: string; message?: string }>;
}> {
async handler(resources): Promise<Pick<SavedObservabilityOnboardingFlow, 'progress'>> {
const {
params: {
path: { onboardingId },
@ -154,21 +157,11 @@ const getProgressRoute = createObservabilityOnboardingServerRoute({
const esClient = coreStart.elasticsearch.client.asScoped(request).asCurrentUser;
const type = savedObservabilityOnboardingState.type;
if (progress['ea-status']?.status === 'complete') {
const { agentId } = progress['ea-status']?.payload as ElasticAgentStepPayload;
try {
const hasLogs = await getHasLogs({
type,
state: savedObservabilityOnboardingState.state,
esClient,
payload: progress['ea-status']?.payload as ElasticAgentStepPayload,
});
if (hasLogs) {
progress['logs-ingest'] = { status: 'complete' };
} else {
progress['logs-ingest'] = { status: 'loading' };
}
const hasLogs = await getHasLogs(esClient, agentId);
progress['logs-ingest'] = { status: hasLogs ? 'complete' : 'loading' };
} catch (error) {
progress['logs-ingest'] = { status: 'warning', message: error.message };
}
@ -180,6 +173,88 @@ const getProgressRoute = createObservabilityOnboardingServerRoute({
},
});
/**
* This endpoint starts a new onboarding flow and creates two API keys:
* 1. A short-lived API key with privileges to install integrations.
* 2. An API key with privileges to ingest log and metric data used to configure Elastic Agent.
*
* It also returns all required information to download the onboarding script and install the
* Elastic agent.
*
* If the user does not have all necessary privileges a 403 Forbidden response is returned.
*
* This endpoint differs from the existing `POST /internal/observability_onboarding/logs/flow`
* endpoint in that it caters for the auto-detect flow where integrations are detected and installed
* on the host system, rather than in the Kiabana UI.
*/
const createFlowRoute = createObservabilityOnboardingServerRoute({
endpoint: 'POST /internal/observability_onboarding/flow',
options: { tags: [] },
params: t.type({
body: t.type({
name: t.string,
}),
}),
async handler(resources) {
const {
context,
params: {
body: { name },
},
core,
request,
plugins,
kibanaVersion,
} = resources;
const coreStart = await core.start();
const {
elasticsearch: { client },
} = await context.core;
const savedObjectsClient = coreStart.savedObjects.getScopedClient(request);
const hasPrivileges = await hasLogMonitoringPrivileges(client.asCurrentUser);
if (!hasPrivileges) {
throw Boom.forbidden('Unauthorized to create log indices');
}
const fleetPluginStart = await plugins.fleet.start();
const securityPluginStart = await plugins.security.start();
const [onboardingFlow, ingestApiKey, installApiKey, elasticAgentVersion] = await Promise.all([
saveObservabilityOnboardingFlow({
savedObjectsClient,
observabilityOnboardingState: {
type: 'autoDetect',
state: undefined,
progress: {},
},
}),
createShipperApiKey(client.asCurrentUser, name),
securityPluginStart.authc.apiKeys.create(request, createInstallApiKey(name)),
getAgentVersion(fleetPluginStart, kibanaVersion),
]);
if (!installApiKey) {
throw Boom.notFound('License does not allow API key creation.');
}
const kibanaUrl = getKibanaUrl(core.setup, plugins.cloud?.setup);
const scriptDownloadUrl = new URL(
core.setup.http.staticAssets.getPluginAssetHref('auto_detect.sh'),
kibanaUrl
).toString();
return {
onboardingFlow,
ingestApiKey: ingestApiKey.encoded,
installApiKey: installApiKey.encoded,
elasticAgentVersion,
kibanaUrl,
scriptDownloadUrl,
};
},
});
/**
* This endpoints installs the requested integrations and returns the corresponding config file for Elastic Agent.
*
@ -239,9 +314,12 @@ const integrationsInstallRoute = createObservabilityOnboardingServerRoute({
});
}
let agentPolicyInputs: TemplateAgentPolicyInput[] = [];
let installedIntegrations: InstalledIntegration[] = [];
try {
agentPolicyInputs = await ensureInstalledIntegrations(integrationsToInstall, packageClient);
installedIntegrations = await ensureInstalledIntegrations(
integrationsToInstall,
packageClient
);
} catch (error) {
if (error instanceof FleetUnauthorizedError) {
return response.forbidden({
@ -262,10 +340,10 @@ const integrationsInstallRoute = createObservabilityOnboardingServerRoute({
...savedObservabilityOnboardingState.progress,
'install-integrations': {
status: 'complete',
payload: integrationsToInstall,
payload: installedIntegrations,
},
},
} as ObservabilityOnboardingFlow,
},
});
const elasticsearchUrl = plugins.cloud?.setup?.elasticsearchUrl
@ -278,55 +356,89 @@ const integrationsInstallRoute = createObservabilityOnboardingServerRoute({
},
body: generateAgentConfig({
esHost: elasticsearchUrl,
inputs: agentPolicyInputs,
inputs: installedIntegrations.map(({ inputs }) => inputs).flat(),
}),
});
},
});
export interface RegistryIntegrationToInstall {
pkgName: string;
installSource: 'registry';
}
export interface CustomIntegrationToInstall {
pkgName: string;
installSource: 'custom';
logFilePaths: string[];
}
export type IntegrationToInstall = RegistryIntegrationToInstall | CustomIntegrationToInstall;
async function ensureInstalledIntegrations(
integrationsToInstall: Integration[],
integrationsToInstall: IntegrationToInstall[],
packageClient: PackageClient
) {
const agentPolicyInputs: TemplateAgentPolicyInput[] = [];
for (const integration of integrationsToInstall) {
const { pkgName, installSource } = integration;
if (installSource === 'registry') {
const pkg = await packageClient.ensureInstalledPackage({ pkgName });
const inputs = await packageClient.getAgentPolicyInputs(pkg.name, pkg.version);
agentPolicyInputs.push(...inputs.filter((input) => input.type !== 'httpjson'));
} else if (installSource === 'custom') {
const input: TemplateAgentPolicyInput = {
id: `filestream-${pkgName}`,
type: 'filestream',
streams: [
): Promise<InstalledIntegration[]> {
return Promise.all(
integrationsToInstall.map(async (integration) => {
const { pkgName, installSource } = integration;
if (installSource === 'registry') {
const pkg = await packageClient.ensureInstalledPackage({ pkgName });
const inputs = await packageClient.getAgentPolicyInputs(pkg.name, pkg.version);
const { packageInfo } = await packageClient.getPackage(pkg.name, pkg.version);
return {
installSource,
pkgName: pkg.name,
pkgVersion: pkg.version,
title: packageInfo.title,
inputs: inputs.filter((input) => input.type !== 'httpjson'),
dataStreams:
packageInfo.data_streams?.map(({ type, dataset }) => ({ type, dataset })) ?? [],
kibanaAssets: pkg.installed_kibana,
};
}
const dataStream = {
type: 'logs',
dataset: pkgName,
};
const installed: InstalledIntegration = {
installSource,
pkgName,
pkgVersion: '1.0.0', // Custom integrations are always installed as version `1.0.0`
title: pkgName,
inputs: [
{
id: `filestream-${pkgName}`,
data_stream: {
type: 'logs',
dataset: pkgName,
},
paths: integration.logFilePaths,
type: 'filestream',
streams: [
{
id: `filestream-${pkgName}`,
data_stream: dataStream,
paths: integration.logFilePaths,
},
],
},
],
dataStreams: [dataStream],
kibanaAssets: [],
};
try {
await packageClient.installCustomIntegration({
pkgName,
datasets: [{ name: pkgName, type: 'logs' }],
datasets: [{ name: dataStream.dataset, type: dataStream.type as PackageDataStreamTypes }],
});
agentPolicyInputs.push(input);
return installed;
} catch (error) {
// If the error is a naming collision, we can assume the integration is already installed and treat this step as successful
if (error instanceof NamingCollisionError) {
agentPolicyInputs.push(input);
return installed;
} else {
throw error;
}
}
}
}
return agentPolicyInputs;
})
);
}
/**
@ -347,48 +459,46 @@ async function ensureInstalledIntegrations(
function parseIntegrationsTSV(tsv: string) {
return Object.values(
tsv
.trim()
.split('\n')
.map((line) => line.split('\t', 3))
.reduce<Record<string, Integration>>((acc, [pkgName, installSource, logFilePath]) => {
const key = `${pkgName}-${installSource}`;
if (installSource === 'registry') {
if (logFilePath) {
throw new Error(`Integration '${pkgName}' does not support a file path`);
}
acc[key] = {
pkgName,
installSource,
};
return acc;
} else if (installSource === 'custom') {
if (!logFilePath) {
throw new Error(`Missing file path for integration: ${pkgName}`);
}
// Append file path if integration is already in the list
const existing = acc[key];
if (existing && existing.installSource === 'custom') {
existing.logFilePaths.push(logFilePath);
.reduce<Record<string, IntegrationToInstall>>(
(acc, [pkgName, installSource, logFilePath]) => {
const key = `${pkgName}-${installSource}`;
if (installSource === 'registry') {
if (logFilePath) {
throw new Error(`Integration '${pkgName}' does not support a file path`);
}
acc[key] = {
pkgName,
installSource,
};
return acc;
} else if (installSource === 'custom') {
if (!logFilePath) {
throw new Error(`Missing file path for integration: ${pkgName}`);
}
// Append file path if integration is already in the list
const existing = acc[key];
if (existing && existing.installSource === 'custom') {
existing.logFilePaths.push(logFilePath);
return acc;
}
acc[key] = {
pkgName,
installSource,
logFilePaths: [logFilePath],
};
return acc;
}
acc[key] = {
pkgName,
installSource,
logFilePaths: [logFilePath],
};
return acc;
}
throw new Error(`Invalid install source: ${installSource}`);
}, {})
throw new Error(`Invalid install source: ${installSource}`);
},
{}
)
);
}
const generateAgentConfig = ({
esHost,
inputs = [],
}: {
esHost: string[];
inputs: TemplateAgentPolicyInput[];
}) => {
const generateAgentConfig = ({ esHost, inputs = [] }: { esHost: string[]; inputs: unknown[] }) => {
return dump({
outputs: {
default: {
@ -402,6 +512,7 @@ const generateAgentConfig = ({
};
export const flowRouteRepository = {
...createFlowRoute,
...updateOnboardingFlowRoute,
...stepProgressUpdateRoute,
...getProgressRoute,

View file

@ -0,0 +1,40 @@
/*
* 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 { ALL_SPACES_ID } from '@kbn/spaces-plugin/common/constants';
import type { CreateAPIKeyParams } from '@kbn/security-plugin/server';
/**
* Creates a short lived API key with the necessary permissions to install integrations
*/
export function createInstallApiKey(name: string): CreateAPIKeyParams {
return {
name: `onboarding_install_${name}`,
expiration: '1h', // This API key is only used for initial setup and should be short lived
metadata: {
managed: true,
application: 'logs',
},
kibana_role_descriptors: {
can_install_integrations: {
elasticsearch: {
cluster: [],
indices: [],
},
kibana: [
{
feature: {
fleet: ['all'],
fleetv2: ['all'], // TODO: Remove this once #183020 is resolved
},
spaces: [ALL_SPACES_ID],
},
],
},
},
};
}

View file

@ -13,7 +13,10 @@ export function createShipperApiKey(esClient: ElasticsearchClient, name: string)
return esClient.security.createApiKey({
body: {
name: `standalone_agent_logs_onboarding_${name}`,
metadata: { application: 'logs' },
metadata: {
managed: true,
application: 'logs',
},
role_descriptors: {
standalone_agent: {
cluster,

View file

@ -7,7 +7,8 @@
import * as t from 'io-ts';
import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route';
import { getFallbackKibanaUrl } from '../../lib/get_fallback_urls';
import { getKibanaUrl } from '../../lib/get_fallback_urls';
import { getAgentVersion } from '../../lib/get_agent_version';
import { hasLogMonitoringPrivileges } from './api_key/has_log_monitoring_privileges';
import { saveObservabilityOnboardingFlow } from '../../lib/state';
import { createShipperApiKey } from './api_key/create_shipper_api_key';
@ -39,27 +40,12 @@ const installShipperSetupRoute = createObservabilityOnboardingServerRoute({
elasticAgentVersion: string;
}> {
const { core, plugins, kibanaVersion } = resources;
const coreStart = await core.start();
const fleetPluginStart = await plugins.fleet.start();
const agentClient = fleetPluginStart.agentService.asInternalUser;
// If undefined, we will follow fleet's strategy to select latest available version:
// for serverless we will use the latest published version, for statefull we will use
// current Kibana version. If false, irrespective of fleet flags and logic, we are
// explicitly deciding to not append the current version.
const includeCurrentVersion = kibanaVersion.endsWith('-SNAPSHOT') ? false : undefined;
const elasticAgentVersion = await agentClient.getLatestAgentAvailableVersion(
includeCurrentVersion
);
const kibanaUrl =
core.setup.http.basePath.publicBaseUrl ?? // priority given to server.publicBaseUrl
plugins.cloud?.setup?.kibanaUrl ?? // then cloud id
getFallbackKibanaUrl(coreStart); // falls back to local network binding
const elasticAgentVersion = await getAgentVersion(fleetPluginStart, kibanaVersion);
const kibanaUrl = getKibanaUrl(core.setup, plugins.cloud?.setup);
const scriptDownloadUrl = new URL(
coreStart.http.staticAssets.getPluginAssetHref('standalone_agent_setup.sh'),
core.setup.http.staticAssets.getPluginAssetHref('standalone_agent_setup.sh'),
kibanaUrl
).toString();

View file

@ -52,19 +52,27 @@ export interface ObservabilityOnboardingRouteCreateOptions {
};
}
export const IntegrationRT = t.union([
t.type({
pkgName: t.string,
installSource: t.literal('registry'),
}),
t.type({
pkgName: t.string,
installSource: t.literal('custom'),
logFilePaths: t.array(t.string),
}),
]);
export const IntegrationRT = t.type({
installSource: t.union([t.literal('registry'), t.literal('custom')]),
pkgName: t.string,
pkgVersion: t.string,
title: t.string,
inputs: t.array(t.unknown),
dataStreams: t.array(
t.type({
type: t.string,
dataset: t.string,
})
),
kibanaAssets: t.array(
t.type({
type: t.string,
id: t.string,
})
),
});
export type Integration = t.TypeOf<typeof IntegrationRT>;
export type InstalledIntegration = t.TypeOf<typeof IntegrationRT>;
export const ElasticAgentStepPayloadRT = t.type({
agentId: t.string,

View file

@ -23,7 +23,7 @@ export interface SystemLogsState {
namespace: string;
}
export type ObservabilityOnboardingType = 'logFiles' | 'systemLogs';
export type ObservabilityOnboardingType = 'logFiles' | 'systemLogs' | 'autoDetect';
type ObservabilityOnboardingFlowState = LogFilesState | SystemLogsState | undefined;
@ -64,8 +64,21 @@ const ElasticAgentStepPayloadSchema = schema.object({
export const InstallIntegrationsStepPayloadSchema = schema.arrayOf(
schema.object({
pkgName: schema.string(),
installSource: schema.string(),
logFilePaths: schema.maybe(schema.arrayOf(schema.string())),
pkgVersion: schema.string(),
installSource: schema.oneOf([schema.literal('registry'), schema.literal('custom')]),
inputs: schema.arrayOf(schema.any()),
dataStreams: schema.arrayOf(
schema.object({
type: schema.string(),
dataset: schema.string(),
})
),
kibanaAssets: schema.arrayOf(
schema.object({
type: schema.string(),
id: schema.string(),
})
),
})
);

View file

@ -12,6 +12,7 @@ import {
PluginStart as DataPluginStart,
} from '@kbn/data-plugin/server';
import { FleetSetupContract, FleetStartContract } from '@kbn/fleet-plugin/server';
import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server';
import { ObservabilityPluginSetup } from '@kbn/observability-plugin/server';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
@ -21,6 +22,7 @@ export interface ObservabilityOnboardingPluginSetupDependencies {
cloud: CloudSetup;
usageCollection: UsageCollectionSetup;
fleet: FleetSetupContract;
security: SecurityPluginSetup;
}
export interface ObservabilityOnboardingPluginStartDependencies {
@ -29,6 +31,7 @@ export interface ObservabilityOnboardingPluginStartDependencies {
cloud: CloudStart;
usageCollection: undefined;
fleet: FleetStartContract;
security: SecurityPluginStart;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface

View file

@ -38,7 +38,14 @@
"@kbn/home-sample-data-tab",
"@kbn/react-kibana-context-render",
"@kbn/react-kibana-context-theme",
"@kbn/ebt"
"@kbn/discover-plugin",
"@kbn/utility-types",
"@kbn/spaces-plugin",
"@kbn/ebt",
"@kbn/dashboard-plugin",
"@kbn/deeplinks-analytics"
],
"exclude": ["target/**/*"]
"exclude": [
"target/**/*"
]
}