mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Fleet] Display next steps and actions in agentless integrations flyout (#203824)
## Summary
Display next steps and actions in agentless integrations flyout. This PR
is based off the following changes:
**Agentless flyout**
Introduced with https://github.com/elastic/kibana/pull/199567
**package-spec**
The definitions for package-spec have been updated in these two PRs:
- https://github.com/elastic/package-spec/pull/834
- https://github.com/elastic/package-spec/pull/844
Any agentless package can now define internal links with format
`kbn:/app/...` and external links with format `https://...`. This PR
shows a card or a button linking to these urls in the new agentless
flyout
**Connectors**
Agentless integration now expose connectors name and id in the package
policy (see code
[here](69fd5a26c4/packages/elastic_connectors/manifest.yml (L45-L62)
)
for elastic connectors integration).
<img width="1003" alt="Screenshot 2024-12-16 at 16 30 22"
src="https://github.com/user-attachments/assets/70b3471e-51bb-4e79-95a4-843e20128c26"
/>
This PR creates a dynamic link to the connector configured in the policy
and shows it in the agentless flyout.
### Testing
- First of all, enable agentless following the steps under `Testing` in
[ this PR](https://github.com/elastic/kibana/pull/199567). Follow up to
step 3
- Instead of installing CSPM, install this test package
[agentless_package_links-0.0.1.zip](https://github.com/user-attachments/files/18152872/agentless_package_links-0.0.1.zip)
with the upload command
```
curl -XPOST -H 'content-type: application/zip' -H 'kbn-xsrf: true' http://localhost:5601/YOURPATH/api/fleet/epm/packages -u elastic:changeme --data-binary @agentless_package_links-0.0.1.zip
```
- Once appears installed, create a package policy with this new
integration. Make sure to choose `agentless` as deployment mode
<img width="1278" alt="Screenshot 2024-12-16 at 16 22 09"
src="https://github.com/user-attachments/assets/7104bf2a-e419-4efa-b352-278ad2057951"
/>
- Enroll an agent to the newly created "agentless" policy by using the
token (it's available in the token page)
- Go back to integrations, you should see a page like this one:
<img width="1569" alt="Screenshot 2024-12-16 at 16 38 18"
src="https://github.com/user-attachments/assets/de770984-985e-449e-b6e3-5c78eb5d3926"
/>
- Click on the state ("pending"/"healhty"/"unhealthy") and see the
flyout. If the enrollment was successful, you should see some cards and
buttons that link to internal and external links in kibana
<img width="878" alt="Screenshot 2024-12-16 at 16 21 57"
src="https://github.com/user-attachments/assets/c77b224f-882c-4d52-956a-744e94e36f1e"
/>
### Testing the connector cards
- First create a new connector: go to
`app/elasticsearch/content/connectors` and click on "new connector". For
this purpose there's no need to complete the procedure
- Note down the name and id of the connector
<img width="1789" alt="Screenshot 2024-12-16 at 16 42 00"
src="https://github.com/user-attachments/assets/b60e491c-809a-40d5-8d01-12b225896fca"
/>
<img width="1789" alt="Screenshot 2024-12-16 at 16 42 00"
src="https://github.com/user-attachments/assets/1adc65e4-0b3b-4e03-9e65-5cc01385b0db"
/>
- Go back to the integration policy previously installed. Enable the
"Test Connector" input and add the name and id from above.
- The agentless flyout should now have a card that will link the user to
`app/elasticsearch/content/connectors/<id>`
### Checklist
- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
---------
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e012c49905
commit
790c58932f
6 changed files with 180 additions and 9 deletions
|
@ -8,3 +8,4 @@
|
|||
export const PLUGIN_ID = 'fleet' as const;
|
||||
export const INTEGRATIONS_PLUGIN_ID = 'integrations' as const;
|
||||
export const TRANSFORM_PLUGIN_ID = 'transform' as const;
|
||||
export const ELASTICSEARCH_PLUGIN_ID = 'elasticsearch' as const;
|
||||
|
|
|
@ -207,6 +207,15 @@ export interface DeploymentsModes {
|
|||
default?: DeploymentsModesDefault;
|
||||
}
|
||||
|
||||
type Action = 'action';
|
||||
type NextStep = 'next_step';
|
||||
export interface ConfigurationLink {
|
||||
title: string;
|
||||
url: string;
|
||||
type: Action | NextStep;
|
||||
content?: string;
|
||||
}
|
||||
|
||||
export enum RegistryPolicyTemplateKeys {
|
||||
categories = 'categories',
|
||||
data_streams = 'data_streams',
|
||||
|
@ -223,6 +232,7 @@ export enum RegistryPolicyTemplateKeys {
|
|||
icons = 'icons',
|
||||
screenshots = 'screenshots',
|
||||
deployment_modes = 'deployment_modes',
|
||||
configuration_links = 'configuration_links',
|
||||
}
|
||||
interface BaseTemplate {
|
||||
[RegistryPolicyTemplateKeys.name]: string;
|
||||
|
@ -232,6 +242,7 @@ interface BaseTemplate {
|
|||
[RegistryPolicyTemplateKeys.screenshots]?: RegistryImage[];
|
||||
[RegistryPolicyTemplateKeys.multiple]?: boolean;
|
||||
[RegistryPolicyTemplateKeys.deployment_modes]?: DeploymentsModes;
|
||||
[RegistryPolicyTemplateKeys.configuration_links]?: ConfigurationLink[];
|
||||
}
|
||||
export interface RegistryPolicyIntegrationTemplate extends BaseTemplate {
|
||||
[RegistryPolicyTemplateKeys.categories]?: Array<PackageSpecCategory | undefined>;
|
||||
|
|
|
@ -185,6 +185,7 @@ export const AgentlessEnrollmentFlyout = ({
|
|||
<AgentlessStepConfirmData
|
||||
agent={agentData}
|
||||
packagePolicy={packagePolicy}
|
||||
policyTemplates={packageInfoData?.item.policy_templates}
|
||||
setConfirmDataStatus={setConfirmDataStatus}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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, { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
EuiSpacer,
|
||||
EuiFlexItem,
|
||||
EuiCard,
|
||||
EuiFlexGroup,
|
||||
EuiButton,
|
||||
EuiHorizontalRule,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useStartServices } from '../../hooks';
|
||||
import type { PackagePolicy, RegistryPolicyTemplate } from '../../types';
|
||||
import { ELASTICSEARCH_PLUGIN_ID } from '../../../common/constants/plugin';
|
||||
|
||||
export const NextSteps = ({
|
||||
packagePolicy,
|
||||
policyTemplates,
|
||||
}: {
|
||||
packagePolicy: PackagePolicy;
|
||||
policyTemplates?: RegistryPolicyTemplate[];
|
||||
}) => {
|
||||
const { application } = useStartServices();
|
||||
|
||||
const configurationLinks = useMemo(() => {
|
||||
if (policyTemplates) {
|
||||
return policyTemplates
|
||||
?.filter(
|
||||
(template) => template?.configuration_links && template.configuration_links.length > 0
|
||||
)
|
||||
.flatMap((template) => template.configuration_links);
|
||||
}
|
||||
return [];
|
||||
}, [policyTemplates]);
|
||||
|
||||
const parseKbnLink = (url: string) => {
|
||||
// matching strings with format kbn:/app/appId/path/optionalsubpath
|
||||
const matches = url.match(/kbn:\/app\/(\w*)\/(\w*\/*)*/);
|
||||
if (matches && matches.length > 0) {
|
||||
const appId = matches[1];
|
||||
const path = matches[2];
|
||||
return { appId, path };
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const isExternal = (url: string) => url.startsWith('http') || url.startsWith('https');
|
||||
const onClickLink = useCallback(
|
||||
(url?: string) => {
|
||||
if (!url) return undefined;
|
||||
|
||||
if (isExternal(url)) {
|
||||
application.navigateToUrl(`${url}`);
|
||||
} else if (url.startsWith('kbn:/')) {
|
||||
const parsedLink = parseKbnLink(url);
|
||||
if (parsedLink) {
|
||||
const { appId, path } = parsedLink;
|
||||
application.navigateToApp(appId, {
|
||||
path,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[application]
|
||||
);
|
||||
|
||||
const nextStepsCards = configurationLinks
|
||||
.filter((link) => link?.type === 'next_step')
|
||||
.map((link, index) => {
|
||||
return (
|
||||
<EuiFlexItem key={index}>
|
||||
<EuiCard
|
||||
data-test-subj={`agentlessStepConfirmData.connectorCard.${link?.title}`}
|
||||
title={`${link?.title}`}
|
||||
description={`${link?.content}`}
|
||||
onClick={() => onClickLink(link?.url)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
});
|
||||
|
||||
const connectorCards = packagePolicy.inputs
|
||||
.filter((input) => !!input?.vars?.connector_id.value || !!input?.vars?.connector_name.value)
|
||||
.map((input, index) => {
|
||||
return (
|
||||
<EuiFlexItem key={index}>
|
||||
<EuiCard
|
||||
data-test-subj={`agentlessStepConfirmData.connectorCard.${input?.vars?.connector_name.value}`}
|
||||
title={`${input?.vars?.connector_name.value}`}
|
||||
description={i18n.translate(
|
||||
'xpack.fleet.agentlessStepConfirmData.connectorCard.description',
|
||||
{
|
||||
defaultMessage: 'Configure Connector',
|
||||
}
|
||||
)}
|
||||
onClick={() => {
|
||||
application.navigateToApp(ELASTICSEARCH_PLUGIN_ID, {
|
||||
path: input?.vars?.connector_id.value
|
||||
? `content/connectors/${input?.vars?.connector_id.value}`
|
||||
: `content/connectors`,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
});
|
||||
|
||||
const actionButtons = configurationLinks
|
||||
.filter((link) => !!link && link?.type === 'action')
|
||||
.map((link, index) => {
|
||||
return (
|
||||
<EuiFlexItem key={index} grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj={`agentlessStepConfirmData.connectorCard.${link?.title}`}
|
||||
iconType="link"
|
||||
onClick={() => onClickLink(link?.url)}
|
||||
>
|
||||
{link?.title}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
{nextStepsCards.length > 0 && (
|
||||
<EuiFlexGroup alignItems="center" direction="row" wrap={true}>
|
||||
{nextStepsCards}
|
||||
{connectorCards}
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
<EuiHorizontalRule />
|
||||
{actionButtons.length > 0 && (
|
||||
<EuiFlexGroup alignItems="center" direction="row" wrap={true}>
|
||||
{actionButtons}
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -12,20 +12,24 @@ import type { EuiStepStatus } from '@elastic/eui';
|
|||
import { EuiText, EuiLink, EuiSpacer, EuiCallOut } from '@elastic/eui';
|
||||
|
||||
import { useStartServices } from '../../hooks';
|
||||
import type { Agent, PackagePolicy } from '../../types';
|
||||
import type { Agent, PackagePolicy, RegistryPolicyTemplate } from '../../types';
|
||||
import {
|
||||
usePollingIncomingData,
|
||||
POLLING_TIMEOUT_MS,
|
||||
} from '../agent_enrollment_flyout/use_get_agent_incoming_data';
|
||||
|
||||
import { NextSteps } from './next_steps';
|
||||
|
||||
export const AgentlessStepConfirmData = ({
|
||||
agent,
|
||||
packagePolicy,
|
||||
setConfirmDataStatus,
|
||||
policyTemplates,
|
||||
}: {
|
||||
agent: Agent;
|
||||
packagePolicy: PackagePolicy;
|
||||
setConfirmDataStatus: (status: EuiStepStatus) => void;
|
||||
policyTemplates?: RegistryPolicyTemplate[];
|
||||
}) => {
|
||||
const { docLinks } = useStartServices();
|
||||
const [overallState, setOverallState] = useState<'pending' | 'success' | 'failure'>('pending');
|
||||
|
@ -53,13 +57,17 @@ export const AgentlessStepConfirmData = ({
|
|||
|
||||
if (overallState === 'success') {
|
||||
return (
|
||||
<EuiCallOut
|
||||
color="success"
|
||||
title={i18n.translate('xpack.fleet.agentlessEnrollmentFlyout.confirmData.successText', {
|
||||
defaultMessage: 'Incoming data received from agentless integration',
|
||||
})}
|
||||
iconType="check"
|
||||
/>
|
||||
<>
|
||||
<EuiCallOut
|
||||
color="success"
|
||||
title={i18n.translate('xpack.fleet.agentlessEnrollmentFlyout.confirmData.successText', {
|
||||
defaultMessage: 'Incoming data received from agentless integration',
|
||||
})}
|
||||
iconType="check"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<NextSteps packagePolicy={packagePolicy} policyTemplates={policyTemplates} />
|
||||
</>
|
||||
);
|
||||
} else if (overallState === 'failure') {
|
||||
return (
|
||||
|
|
|
@ -271,7 +271,7 @@ export async function getIncomingDataByAgentsId({
|
|||
} catch (error) {
|
||||
logger.debug(`Error getting incoming data for agents: ${error}`);
|
||||
throw new FleetError(
|
||||
`Unable to retrive incoming data for agents due to error: ${error.message}`
|
||||
`Unable to retrieve incoming data for agents due to error: ${error.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue