mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
[Integrations UI] Add support for custom asset definitions in Integration assets tab (#103554)
* Add UI extension logic for assets + set up custom log views * Add endpoint security UI extension * Add synthetics ui extension * Address PR feedback - Remove default filter for log stream url - Fix missing basePath prepend on asset urls - Expand accordion by default on assetless integrations * Fix type errors * Add initial APM extension setup * Fix missing ExtensionWrapper for enrollment extension * Fix custom logs asset extension * Fix type errors * Add new hook for enrollment flyout ui extensions * Address PR review + refactor UI extension usage for flyout * Update limits.yml via script * Fix type errors * Add tests for custom assets UI extensions * Update tests for flyout * Remove unused import * Fix type errors in ui extension tests * Skip view data tests and link to issue * Use RedirectAppLinks + fix synthetics link * Use constants for app ID's where possible * Revert limits.yml * Fix lazy imports for custom asset components * Update endpoint custom assets link + description * Add translation for custom assets UI * Address PR review in APM Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
1c82ad2c95
commit
059ed0821a
39 changed files with 671 additions and 163 deletions
|
@ -3,33 +3,34 @@
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"kibanaVersion": "kibana",
|
"kibanaVersion": "kibana",
|
||||||
"requiredPlugins": [
|
"requiredPlugins": [
|
||||||
"features",
|
|
||||||
"apmOss",
|
"apmOss",
|
||||||
"data",
|
"data",
|
||||||
"licensing",
|
|
||||||
"triggersActionsUi",
|
|
||||||
"embeddable",
|
"embeddable",
|
||||||
|
"features",
|
||||||
|
"fleet",
|
||||||
"infra",
|
"infra",
|
||||||
|
"licensing",
|
||||||
"observability",
|
"observability",
|
||||||
"ruleRegistry"
|
"ruleRegistry",
|
||||||
|
"triggersActionsUi"
|
||||||
],
|
],
|
||||||
"optionalPlugins": [
|
"optionalPlugins": [
|
||||||
"spaces",
|
|
||||||
"cloud",
|
|
||||||
"usageCollection",
|
|
||||||
"taskManager",
|
|
||||||
"actions",
|
"actions",
|
||||||
"alerting",
|
"alerting",
|
||||||
"security",
|
"cloud",
|
||||||
"ml",
|
|
||||||
"home",
|
"home",
|
||||||
"maps",
|
"maps",
|
||||||
"fleet"
|
"ml",
|
||||||
|
"security",
|
||||||
|
"spaces",
|
||||||
|
"taskManager",
|
||||||
|
"usageCollection"
|
||||||
],
|
],
|
||||||
"server": true,
|
"server": true,
|
||||||
"ui": true,
|
"ui": true,
|
||||||
"configPath": ["xpack", "apm"],
|
"configPath": ["xpack", "apm"],
|
||||||
"requiredBundles": [
|
"requiredBundles": [
|
||||||
|
"fleet",
|
||||||
"home",
|
"home",
|
||||||
"kibanaReact",
|
"kibanaReact",
|
||||||
"kibanaUtils",
|
"kibanaUtils",
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CustomAssetsAccordionProps,
|
||||||
|
CustomAssetsAccordion,
|
||||||
|
} from '../../../../fleet/public';
|
||||||
|
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||||
|
import { ApmPluginStartDeps } from '../../plugin';
|
||||||
|
|
||||||
|
export function ApmCustomAssetsExtension() {
|
||||||
|
const { http } = useKibana<ApmPluginStartDeps>().services;
|
||||||
|
const basePath = http?.basePath.get();
|
||||||
|
|
||||||
|
const views: CustomAssetsAccordionProps['views'] = [
|
||||||
|
{
|
||||||
|
name: i18n.translate('xpack.apm.fleetIntegration.assets.name', {
|
||||||
|
defaultMessage: 'Services',
|
||||||
|
}),
|
||||||
|
url: `${basePath}/app/apm`,
|
||||||
|
description: i18n.translate(
|
||||||
|
'xpack.apm.fleetIntegration.assets.description',
|
||||||
|
{ defaultMessage: 'View application traces and service maps in APM' }
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return <CustomAssetsAccordion views={views} initialIsOpen />;
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { EuiButton, EuiText, EuiSpacer } from '@elastic/eui';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
|
import { AgentEnrollmentFlyoutFinalStepExtension } from '../../../../fleet/public';
|
||||||
|
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||||
|
import { ApmPluginStartDeps } from '../../plugin';
|
||||||
|
|
||||||
|
function StepComponent() {
|
||||||
|
const { http } = useKibana<ApmPluginStartDeps>().services;
|
||||||
|
const installApmAgentLink = http?.basePath.prepend('/app/home#/tutorial/apm');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EuiText>
|
||||||
|
<p>
|
||||||
|
{i18n.translate(
|
||||||
|
'xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentDescription',
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
'After the agent starts, you can install APM agents on your hosts to collect data from your applications and services.',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</EuiText>
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
|
||||||
|
<EuiButton fill href={installApmAgentLink}>
|
||||||
|
{i18n.translate(
|
||||||
|
'xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentButtonText',
|
||||||
|
{ defaultMessage: 'Install APM Agent' }
|
||||||
|
)}
|
||||||
|
</EuiButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getApmEnrollmentFlyoutData(): Pick<
|
||||||
|
AgentEnrollmentFlyoutFinalStepExtension,
|
||||||
|
'title' | 'Component'
|
||||||
|
> {
|
||||||
|
return {
|
||||||
|
title: i18n.translate(
|
||||||
|
'xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentTitle',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Install APM Agent',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Component: StepComponent,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './apm_enrollment_flyout_extension';
|
||||||
|
export * from './lazy_apm_custom_assets_extension';
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* 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 { lazy } from 'react';
|
||||||
|
|
||||||
|
export const LazyApmCustomAssetsExtension = lazy(async () => {
|
||||||
|
const { ApmCustomAssetsExtension } = await import(
|
||||||
|
'./apm_custom_assets_extension'
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
default: ApmCustomAssetsExtension,
|
||||||
|
};
|
||||||
|
});
|
|
@ -22,6 +22,7 @@ import type {
|
||||||
DataPublicPluginStart,
|
DataPublicPluginStart,
|
||||||
} from '../../../../src/plugins/data/public';
|
} from '../../../../src/plugins/data/public';
|
||||||
import type { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
|
import type { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
|
||||||
|
import type { FleetStart } from '../../fleet/public';
|
||||||
import type { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
|
import type { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
|
||||||
import type {
|
import type {
|
||||||
PluginSetupContract as AlertingPluginPublicSetup,
|
PluginSetupContract as AlertingPluginPublicSetup,
|
||||||
|
@ -43,6 +44,10 @@ import type {
|
||||||
} from '../../triggers_actions_ui/public';
|
} from '../../triggers_actions_ui/public';
|
||||||
import { registerApmAlerts } from './components/alerting/register_apm_alerts';
|
import { registerApmAlerts } from './components/alerting/register_apm_alerts';
|
||||||
import { featureCatalogueEntry } from './featureCatalogueEntry';
|
import { featureCatalogueEntry } from './featureCatalogueEntry';
|
||||||
|
import {
|
||||||
|
getApmEnrollmentFlyoutData,
|
||||||
|
LazyApmCustomAssetsExtension,
|
||||||
|
} from './components/fleet_integration';
|
||||||
|
|
||||||
export type ApmPluginSetup = ReturnType<ApmPlugin['setup']>;
|
export type ApmPluginSetup = ReturnType<ApmPlugin['setup']>;
|
||||||
|
|
||||||
|
@ -69,6 +74,7 @@ export interface ApmPluginStartDeps {
|
||||||
ml?: MlPluginStart;
|
ml?: MlPluginStart;
|
||||||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||||
observability: ObservabilityPublicStart;
|
observability: ObservabilityPublicStart;
|
||||||
|
fleet: FleetStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
||||||
|
@ -303,5 +309,22 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
public start(core: CoreStart, plugins: ApmPluginStartDeps) {}
|
public start(core: CoreStart, plugins: ApmPluginStartDeps) {
|
||||||
|
const { fleet } = plugins;
|
||||||
|
|
||||||
|
const agentEnrollmentExtensionData = getApmEnrollmentFlyoutData();
|
||||||
|
|
||||||
|
fleet.registerExtension({
|
||||||
|
package: 'apm',
|
||||||
|
view: 'agent-enrollment-flyout',
|
||||||
|
title: agentEnrollmentExtensionData.title,
|
||||||
|
Component: agentEnrollmentExtensionData.Component,
|
||||||
|
});
|
||||||
|
|
||||||
|
fleet.registerExtension({
|
||||||
|
package: 'apm',
|
||||||
|
view: 'package-detail-assets',
|
||||||
|
Component: LazyApmCustomAssetsExtension,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -425,7 +425,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
||||||
[params, updatePackageInfo, agentPolicy, updateAgentPolicy, queryParamsPolicyId]
|
[params, updatePackageInfo, agentPolicy, updateAgentPolicy, queryParamsPolicyId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create');
|
const extensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create');
|
||||||
|
|
||||||
const stepConfigurePackagePolicy = useMemo(
|
const stepConfigurePackagePolicy = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -444,7 +444,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Only show the out-of-box configuration step if a UI extension is NOT registered */}
|
{/* Only show the out-of-box configuration step if a UI extension is NOT registered */}
|
||||||
{!ExtensionView && (
|
{!extensionView && (
|
||||||
<StepConfigurePackagePolicy
|
<StepConfigurePackagePolicy
|
||||||
packageInfo={packageInfo}
|
packageInfo={packageInfo}
|
||||||
showOnlyIntegration={integrationInfo?.name}
|
showOnlyIntegration={integrationInfo?.name}
|
||||||
|
@ -456,9 +456,12 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* If an Agent Policy and a package has been selected, then show UI extension (if any) */}
|
{/* If an Agent Policy and a package has been selected, then show UI extension (if any) */}
|
||||||
{ExtensionView && packagePolicy.policy_id && packagePolicy.package?.name && (
|
{extensionView && packagePolicy.policy_id && packagePolicy.package?.name && (
|
||||||
<ExtensionWrapper>
|
<ExtensionWrapper>
|
||||||
<ExtensionView newPolicy={packagePolicy} onChange={handleExtensionViewOnChange} />
|
<extensionView.Component
|
||||||
|
newPolicy={packagePolicy}
|
||||||
|
onChange={handleExtensionViewOnChange}
|
||||||
|
/>
|
||||||
</ExtensionWrapper>
|
</ExtensionWrapper>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -474,7 +477,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
||||||
validationResults,
|
validationResults,
|
||||||
formState,
|
formState,
|
||||||
integrationInfo?.name,
|
integrationInfo?.name,
|
||||||
ExtensionView,
|
extensionView,
|
||||||
handleExtensionViewOnChange,
|
handleExtensionViewOnChange,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
@ -338,7 +338,7 @@ export const EditPackagePolicyForm = memo<{
|
||||||
packageInfo,
|
packageInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-edit');
|
const extensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-edit');
|
||||||
|
|
||||||
const configurePackage = useMemo(
|
const configurePackage = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -354,7 +354,7 @@ export const EditPackagePolicyForm = memo<{
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Only show the out-of-box configuration step if a UI extension is NOT registered */}
|
{/* Only show the out-of-box configuration step if a UI extension is NOT registered */}
|
||||||
{!ExtensionView && (
|
{!extensionView && (
|
||||||
<StepConfigurePackagePolicy
|
<StepConfigurePackagePolicy
|
||||||
packageInfo={packageInfo}
|
packageInfo={packageInfo}
|
||||||
packagePolicy={packagePolicy}
|
packagePolicy={packagePolicy}
|
||||||
|
@ -364,12 +364,12 @@ export const EditPackagePolicyForm = memo<{
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{ExtensionView &&
|
{extensionView &&
|
||||||
packagePolicy.policy_id &&
|
packagePolicy.policy_id &&
|
||||||
packagePolicy.package?.name &&
|
packagePolicy.package?.name &&
|
||||||
originalPackagePolicy && (
|
originalPackagePolicy && (
|
||||||
<ExtensionWrapper>
|
<ExtensionWrapper>
|
||||||
<ExtensionView
|
<extensionView.Component
|
||||||
policy={originalPackagePolicy}
|
policy={originalPackagePolicy}
|
||||||
newPolicy={packagePolicy}
|
newPolicy={packagePolicy}
|
||||||
onChange={handleExtensionViewOnChange}
|
onChange={handleExtensionViewOnChange}
|
||||||
|
@ -386,7 +386,7 @@ export const EditPackagePolicyForm = memo<{
|
||||||
validationResults,
|
validationResults,
|
||||||
formState,
|
formState,
|
||||||
originalPackagePolicy,
|
originalPackagePolicy,
|
||||||
ExtensionView,
|
extensionView,
|
||||||
handleExtensionViewOnChange,
|
handleExtensionViewOnChange,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,7 +19,7 @@ export const DisplayedAssets: ServiceNameToAssetTypes = {
|
||||||
elasticsearch: Object.values(ElasticsearchAssetType),
|
elasticsearch: Object.values(ElasticsearchAssetType),
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DisplayedAssetType = ElasticsearchAssetType | KibanaAssetType;
|
export type DisplayedAssetType = ElasticsearchAssetType | KibanaAssetType | 'view';
|
||||||
|
|
||||||
export const AssetTitleMap: Record<DisplayedAssetType, string> = {
|
export const AssetTitleMap: Record<DisplayedAssetType, string> = {
|
||||||
dashboard: 'Dashboard',
|
dashboard: 'Dashboard',
|
||||||
|
@ -36,6 +36,7 @@ export const AssetTitleMap: Record<DisplayedAssetType, string> = {
|
||||||
lens: 'Lens',
|
lens: 'Lens',
|
||||||
security_rule: 'Security Rule',
|
security_rule: 'Security Rule',
|
||||||
ml_module: 'ML Module',
|
ml_module: 'ML Module',
|
||||||
|
view: 'Views',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ServiceTitleMap: Record<ServiceName, string> = {
|
export const ServiceTitleMap: Record<ServiceName, string> = {
|
||||||
|
|
|
@ -10,12 +10,17 @@ import { Redirect } from 'react-router-dom';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui';
|
import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui';
|
||||||
|
|
||||||
import { Loading, Error } from '../../../../../components';
|
import { Loading, Error, ExtensionWrapper } from '../../../../../components';
|
||||||
|
|
||||||
import type { PackageInfo } from '../../../../../types';
|
import type { PackageInfo } from '../../../../../types';
|
||||||
import { InstallStatus } from '../../../../../types';
|
import { InstallStatus } from '../../../../../types';
|
||||||
|
|
||||||
import { useGetPackageInstallStatus, useLink, useStartServices } from '../../../../../hooks';
|
import {
|
||||||
|
useGetPackageInstallStatus,
|
||||||
|
useLink,
|
||||||
|
useStartServices,
|
||||||
|
useUIExtension,
|
||||||
|
} from '../../../../../hooks';
|
||||||
|
|
||||||
import type { AssetSavedObject } from './types';
|
import type { AssetSavedObject } from './types';
|
||||||
import { allowedAssetTypes } from './constants';
|
import { allowedAssetTypes } from './constants';
|
||||||
|
@ -27,9 +32,12 @@ interface AssetsPanelProps {
|
||||||
|
|
||||||
export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
|
export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
|
||||||
const { name, version } = packageInfo;
|
const { name, version } = packageInfo;
|
||||||
|
const pkgkey = `${name}-${version}`;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
savedObjects: { client: savedObjectsClient },
|
savedObjects: { client: savedObjectsClient },
|
||||||
} = useStartServices();
|
} = useStartServices();
|
||||||
|
const customAssetsExtension = useUIExtension(packageInfo.name, 'package-detail-assets');
|
||||||
|
|
||||||
const { getPath } = useLink();
|
const { getPath } = useLink();
|
||||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||||
|
@ -76,13 +84,7 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
|
||||||
// if they arrive at this page and the package is not installed, send them to overview
|
// if they arrive at this page and the package is not installed, send them to overview
|
||||||
// this happens if they arrive with a direct url or they uninstall while on this tab
|
// this happens if they arrive with a direct url or they uninstall while on this tab
|
||||||
if (packageInstallStatus.status !== InstallStatus.installed) {
|
if (packageInstallStatus.status !== InstallStatus.installed) {
|
||||||
return (
|
return <Redirect to={getPath('integration_details_overview', { pkgkey })} />;
|
||||||
<Redirect
|
|
||||||
to={getPath('integration_details_overview', {
|
|
||||||
pkgkey: `${name}-${version}`,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let content: JSX.Element | Array<JSX.Element | null>;
|
let content: JSX.Element | Array<JSX.Element | null>;
|
||||||
|
@ -102,6 +104,15 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (assetSavedObjects === undefined) {
|
} else if (assetSavedObjects === undefined) {
|
||||||
|
if (customAssetsExtension) {
|
||||||
|
// If a UI extension for custom asset entries is defined, render the custom component here depisite
|
||||||
|
// there being no saved objects found
|
||||||
|
content = (
|
||||||
|
<ExtensionWrapper>
|
||||||
|
<customAssetsExtension.Component />
|
||||||
|
</ExtensionWrapper>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
content = (
|
content = (
|
||||||
<EuiTitle>
|
<EuiTitle>
|
||||||
<h2>
|
<h2>
|
||||||
|
@ -112,8 +123,10 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
|
||||||
</h2>
|
</h2>
|
||||||
</EuiTitle>
|
</EuiTitle>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
content = allowedAssetTypes.map((assetType) => {
|
content = [
|
||||||
|
...allowedAssetTypes.map((assetType) => {
|
||||||
const sectionAssetSavedObjects = assetSavedObjects.filter((so) => so.type === assetType);
|
const sectionAssetSavedObjects = assetSavedObjects.filter((so) => so.type === assetType);
|
||||||
|
|
||||||
if (!sectionAssetSavedObjects.length) {
|
if (!sectionAssetSavedObjects.length) {
|
||||||
|
@ -126,7 +139,14 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
|
||||||
<EuiSpacer size="l" />
|
<EuiSpacer size="l" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
}),
|
||||||
|
// Ensure we add any custom assets provided via UI extension to the end of the list of other assets
|
||||||
|
customAssetsExtension ? (
|
||||||
|
<ExtensionWrapper>
|
||||||
|
<customAssetsExtension.Component />
|
||||||
|
</ExtensionWrapper>
|
||||||
|
) : null,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -55,6 +55,11 @@ export const AssetsAccordion: FunctionComponent<Props> = ({ savedObjects, type }
|
||||||
<EuiSpacer size="m" />
|
<EuiSpacer size="m" />
|
||||||
<EuiSplitPanel.Outer hasBorder hasShadow={false}>
|
<EuiSplitPanel.Outer hasBorder hasShadow={false}>
|
||||||
{savedObjects.map(({ id, attributes: { title, description } }, idx) => {
|
{savedObjects.map(({ id, attributes: { title, description } }, idx) => {
|
||||||
|
// Ignore custom asset views
|
||||||
|
if (type === 'view') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const pathToObjectInApp = getHrefToObjectInKibanaApp({
|
const pathToObjectInApp = getHrefToObjectInKibanaApp({
|
||||||
http,
|
http,
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -17,4 +17,4 @@ export type AllowedAssetTypes = [
|
||||||
KibanaAssetType.visualization
|
KibanaAssetType.visualization
|
||||||
];
|
];
|
||||||
|
|
||||||
export type AllowedAssetType = AllowedAssetTypes[number];
|
export type AllowedAssetType = AllowedAssetTypes[number] | 'view';
|
||||||
|
|
|
@ -18,16 +18,16 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CustomViewPage: React.FC<Props> = memo(({ packageInfo }) => {
|
export const CustomViewPage: React.FC<Props> = memo(({ packageInfo }) => {
|
||||||
const CustomView = useUIExtension(packageInfo.name, 'package-detail-custom');
|
const customViewExtension = useUIExtension(packageInfo.name, 'package-detail-custom');
|
||||||
const { getPath } = useLink();
|
const { getPath } = useLink();
|
||||||
const pkgkey = useMemo(() => pkgKeyFromPackageInfo(packageInfo), [packageInfo]);
|
const pkgkey = useMemo(() => pkgKeyFromPackageInfo(packageInfo), [packageInfo]);
|
||||||
|
|
||||||
return CustomView ? (
|
return customViewExtension ? (
|
||||||
<EuiFlexGroup alignItems="flexStart">
|
<EuiFlexGroup alignItems="flexStart">
|
||||||
<EuiFlexItem grow={1} />
|
<EuiFlexItem grow={1} />
|
||||||
<EuiFlexItem grow={6}>
|
<EuiFlexItem grow={6}>
|
||||||
<ExtensionWrapper>
|
<ExtensionWrapper>
|
||||||
<CustomView pkgkey={pkgkey} packageInfo={packageInfo} />
|
<customViewExtension.Component pkgkey={pkgkey} packageInfo={packageInfo} />
|
||||||
</ExtensionWrapper>
|
</ExtensionWrapper>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
|
|
@ -113,7 +113,7 @@ describe('when on integration detail', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and a custom UI extension is registered', () => {
|
describe('and a custom tab UI extension is registered', () => {
|
||||||
// Because React Lazy components are loaded async (Promise), we setup this "watcher" Promise
|
// Because React Lazy components are loaded async (Promise), we setup this "watcher" Promise
|
||||||
// that is `resolved` once the lazy components actually renders.
|
// that is `resolved` once the lazy components actually renders.
|
||||||
let lazyComponentWasRendered: Promise<void>;
|
let lazyComponentWasRendered: Promise<void>;
|
||||||
|
@ -136,7 +136,7 @@ describe('when on integration detail', () => {
|
||||||
testRenderer.startInterface.registerExtension({
|
testRenderer.startInterface.registerExtension({
|
||||||
package: 'nginx',
|
package: 'nginx',
|
||||||
view: 'package-detail-custom',
|
view: 'package-detail-custom',
|
||||||
component: CustomComponent,
|
Component: CustomComponent,
|
||||||
});
|
});
|
||||||
|
|
||||||
render();
|
render();
|
||||||
|
@ -162,6 +162,53 @@ describe('when on integration detail', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('and a custom assets UI extension is registered', () => {
|
||||||
|
let lazyComponentWasRendered: Promise<void>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
let setWasRendered: () => void;
|
||||||
|
lazyComponentWasRendered = new Promise((resolve) => {
|
||||||
|
setWasRendered = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
const CustomComponent = lazy(async () => {
|
||||||
|
return {
|
||||||
|
default: memo(() => {
|
||||||
|
setWasRendered();
|
||||||
|
return <div data-test-subj="custom-hello">hello</div>;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
testRenderer.startInterface.registerExtension({
|
||||||
|
package: 'nginx',
|
||||||
|
view: 'package-detail-assets',
|
||||||
|
Component: CustomComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
lazyComponentWasRendered = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display "assets" tab in navigation', () => {
|
||||||
|
expect(renderResult.getByTestId('tab-assets'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display custom assets when tab is clicked', async () => {
|
||||||
|
act(() => {
|
||||||
|
testRenderer.history.push(
|
||||||
|
pagePathGetters.integration_details_assets({ pkgkey: 'nginx-0.3.7' })[1]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await lazyComponentWasRendered;
|
||||||
|
expect(renderResult.getByTestId('custom-hello'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('and the Add integration button is clicked', () => {
|
describe('and the Add integration button is clicked', () => {
|
||||||
beforeEach(() => render());
|
beforeEach(() => render());
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,8 @@ export function Detail() {
|
||||||
const setPackageInstallStatus = useSetPackageInstallStatus();
|
const setPackageInstallStatus = useSetPackageInstallStatus();
|
||||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||||
|
|
||||||
|
const CustomAssets = useUIExtension(packageInfo?.name ?? '', 'package-detail-assets');
|
||||||
|
|
||||||
const packageInstallStatus = useMemo(() => {
|
const packageInstallStatus = useMemo(() => {
|
||||||
if (packageInfo === null || !packageInfo.name) {
|
if (packageInfo === null || !packageInfo.name) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -418,7 +420,7 @@ export function Detail() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packageInstallStatus === InstallStatus.installed && packageInfo.assets) {
|
if (packageInstallStatus === InstallStatus.installed && (packageInfo.assets || CustomAssets)) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
id: 'assets',
|
id: 'assets',
|
||||||
name: (
|
name: (
|
||||||
|
@ -471,7 +473,7 @@ export function Detail() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return tabs;
|
return tabs;
|
||||||
}, [packageInfo, panel, getHref, integration, packageInstallStatus, showCustomTab]);
|
}, [packageInfo, panel, getHref, integration, packageInstallStatus, showCustomTab, CustomAssets]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WithHeaderLayout
|
<WithHeaderLayout
|
||||||
|
|
|
@ -7,7 +7,11 @@
|
||||||
import { stringify, parse } from 'query-string';
|
import { stringify, parse } from 'query-string';
|
||||||
import React, { memo, useCallback, useMemo, useState } from 'react';
|
import React, { memo, useCallback, useMemo, useState } from 'react';
|
||||||
import { Redirect, useLocation, useHistory } from 'react-router-dom';
|
import { Redirect, useLocation, useHistory } from 'react-router-dom';
|
||||||
import type { CriteriaWithPagination, EuiTableFieldDataColumnType } from '@elastic/eui';
|
import type {
|
||||||
|
CriteriaWithPagination,
|
||||||
|
EuiStepProps,
|
||||||
|
EuiTableFieldDataColumnType,
|
||||||
|
} from '@elastic/eui';
|
||||||
import {
|
import {
|
||||||
EuiButtonIcon,
|
EuiButtonIcon,
|
||||||
EuiBasicTable,
|
EuiBasicTable,
|
||||||
|
@ -29,6 +33,7 @@ import {
|
||||||
useUrlPagination,
|
useUrlPagination,
|
||||||
useGetPackageInstallStatus,
|
useGetPackageInstallStatus,
|
||||||
AgentPolicyRefreshContext,
|
AgentPolicyRefreshContext,
|
||||||
|
useUIExtension,
|
||||||
} from '../../../../../hooks';
|
} from '../../../../../hooks';
|
||||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants';
|
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants';
|
||||||
import {
|
import {
|
||||||
|
@ -88,6 +93,8 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
||||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: ${name}`,
|
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: ${name}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const agentEnrollmentFlyoutExtension = useUIExtension(name, 'agent-enrollment-flyout');
|
||||||
|
|
||||||
const handleTableOnChange = useCallback(
|
const handleTableOnChange = useCallback(
|
||||||
({ page }: CriteriaWithPagination<PackagePolicyAndAgentPolicy>) => {
|
({ page }: CriteriaWithPagination<PackagePolicyAndAgentPolicy>) => {
|
||||||
setPagination({
|
setPagination({
|
||||||
|
@ -98,8 +105,19 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
||||||
[setPagination]
|
[setPagination]
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderViewDataStepContent = useCallback(
|
const viewDataStep = useMemo<EuiStepProps>(() => {
|
||||||
() => (
|
if (agentEnrollmentFlyoutExtension) {
|
||||||
|
return {
|
||||||
|
title: agentEnrollmentFlyoutExtension.title,
|
||||||
|
children: <agentEnrollmentFlyoutExtension.Component />,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: i18n.translate('xpack.fleet.agentEnrollment.stepViewDataTitle', {
|
||||||
|
defaultMessage: 'View your data',
|
||||||
|
}),
|
||||||
|
children: (
|
||||||
<>
|
<>
|
||||||
<EuiText>
|
<EuiText>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -125,8 +143,8 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
||||||
</EuiButton>
|
</EuiButton>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
[name, version, getHref]
|
};
|
||||||
);
|
}, [name, version, getHref, agentEnrollmentFlyoutExtension]);
|
||||||
|
|
||||||
const columns: Array<EuiTableFieldDataColumnType<PackagePolicyAndAgentPolicy>> = useMemo(
|
const columns: Array<EuiTableFieldDataColumnType<PackagePolicyAndAgentPolicy>> = useMemo(
|
||||||
() => [
|
() => [
|
||||||
|
@ -230,13 +248,13 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
||||||
<PackagePolicyActionsMenu
|
<PackagePolicyActionsMenu
|
||||||
agentPolicy={agentPolicy}
|
agentPolicy={agentPolicy}
|
||||||
packagePolicy={packagePolicy}
|
packagePolicy={packagePolicy}
|
||||||
viewDataStepContent={renderViewDataStepContent()}
|
viewDataStep={viewDataStep}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[renderViewDataStepContent]
|
[viewDataStep]
|
||||||
);
|
);
|
||||||
|
|
||||||
const noItemsMessage = useMemo(() => {
|
const noItemsMessage = useMemo(() => {
|
||||||
|
@ -292,7 +310,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
||||||
data?.items.find(({ agentPolicy }) => agentPolicy.id === flyoutOpenForPolicyId)
|
data?.items.find(({ agentPolicy }) => agentPolicy.id === flyoutOpenForPolicyId)
|
||||||
?.agentPolicy
|
?.agentPolicy
|
||||||
}
|
}
|
||||||
viewDataStepContent={renderViewDataStepContent()}
|
viewDataStep={viewDataStep}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AgentPolicyRefreshContext.Provider>
|
</AgentPolicyRefreshContext.Provider>
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { FleetStatusProvider, ConfigContext } from '../../hooks';
|
||||||
|
|
||||||
import { useFleetServerInstructions } from '../../applications/fleet/sections/agents/agent_requirements_page/components';
|
import { useFleetServerInstructions } from '../../applications/fleet/sections/agents/agent_requirements_page/components';
|
||||||
|
|
||||||
import { AgentEnrollmentKeySelectionStep, AgentPolicySelectionStep, ViewDataStep } from './steps';
|
import { AgentEnrollmentKeySelectionStep, AgentPolicySelectionStep } from './steps';
|
||||||
|
|
||||||
import type { Props } from '.';
|
import type { Props } from '.';
|
||||||
import { AgentEnrollmentFlyout } from '.';
|
import { AgentEnrollmentFlyout } from '.';
|
||||||
|
@ -129,24 +129,26 @@ describe('<AgentEnrollmentFlyout />', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('"View data" extension point', () => {
|
// Skipped due to implementation details in the step components. See https://github.com/elastic/kibana/issues/103894
|
||||||
it('calls the "View data" step when UI extension is provided', async () => {
|
describe.skip('"View data" extension point', () => {
|
||||||
|
it('shows the "View data" step when UI extension is provided', async () => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
testBed = await setup({
|
testBed = await setup({
|
||||||
agentPolicies: [],
|
agentPolicies: [],
|
||||||
onClose: jest.fn(),
|
onClose: jest.fn(),
|
||||||
viewDataStepContent: <div />,
|
viewDataStep: { title: 'View Data', children: <div /> },
|
||||||
});
|
});
|
||||||
testBed.component.update();
|
testBed.component.update();
|
||||||
});
|
});
|
||||||
const { exists, actions } = testBed;
|
const { exists, actions } = testBed;
|
||||||
expect(exists('agentEnrollmentFlyout')).toBe(true);
|
expect(exists('agentEnrollmentFlyout')).toBe(true);
|
||||||
expect(ViewDataStep).toHaveBeenCalled();
|
expect(exists('view-data-step')).toBe(true);
|
||||||
|
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
actions.goToStandaloneTab();
|
actions.goToStandaloneTab();
|
||||||
expect(ViewDataStep).not.toHaveBeenCalled();
|
expect(exists('agentEnrollmentFlyout')).toBe(true);
|
||||||
|
expect(exists('view-data-step')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not call the "View data" step when UI extension is not provided', async () => {
|
it('does not call the "View data" step when UI extension is not provided', async () => {
|
||||||
|
@ -155,17 +157,17 @@ describe('<AgentEnrollmentFlyout />', () => {
|
||||||
testBed = await setup({
|
testBed = await setup({
|
||||||
agentPolicies: [],
|
agentPolicies: [],
|
||||||
onClose: jest.fn(),
|
onClose: jest.fn(),
|
||||||
viewDataStepContent: undefined,
|
viewDataStep: undefined,
|
||||||
});
|
});
|
||||||
testBed.component.update();
|
testBed.component.update();
|
||||||
});
|
});
|
||||||
const { exists, actions } = testBed;
|
const { exists, actions } = testBed;
|
||||||
expect(exists('agentEnrollmentFlyout')).toBe(true);
|
expect(exists('agentEnrollmentFlyout')).toBe(true);
|
||||||
expect(ViewDataStep).not.toHaveBeenCalled();
|
expect(exists('view-data-step')).toBe(false);
|
||||||
|
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
actions.goToStandaloneTab();
|
actions.goToStandaloneTab();
|
||||||
expect(ViewDataStep).not.toHaveBeenCalled();
|
expect(exists('view-data-step')).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -45,7 +45,7 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({
|
||||||
onClose,
|
onClose,
|
||||||
agentPolicy,
|
agentPolicy,
|
||||||
agentPolicies,
|
agentPolicies,
|
||||||
viewDataStepContent,
|
viewDataStep,
|
||||||
defaultMode = 'managed',
|
defaultMode = 'managed',
|
||||||
}) => {
|
}) => {
|
||||||
const [mode, setMode] = useState<FlyoutMode>(defaultMode);
|
const [mode, setMode] = useState<FlyoutMode>(defaultMode);
|
||||||
|
@ -119,14 +119,10 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({
|
||||||
<ManagedInstructions
|
<ManagedInstructions
|
||||||
agentPolicy={agentPolicy}
|
agentPolicy={agentPolicy}
|
||||||
agentPolicies={agentPolicies}
|
agentPolicies={agentPolicies}
|
||||||
viewDataStepContent={viewDataStepContent}
|
viewDataStep={viewDataStep}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<StandaloneInstructions
|
<StandaloneInstructions agentPolicy={agentPolicy} agentPolicies={agentPolicies} />
|
||||||
agentPolicy={agentPolicy}
|
|
||||||
agentPolicies={agentPolicies}
|
|
||||||
viewDataStepContent={viewDataStepContent}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</EuiFlyoutBody>
|
</EuiFlyoutBody>
|
||||||
<EuiFlyoutFooter>
|
<EuiFlyoutFooter>
|
||||||
|
|
|
@ -23,12 +23,7 @@ import {
|
||||||
} from '../../applications/fleet/sections/agents/agent_requirements_page/components';
|
} from '../../applications/fleet/sections/agents/agent_requirements_page/components';
|
||||||
import { FleetServerRequirementPage } from '../../applications/fleet/sections/agents/agent_requirements_page';
|
import { FleetServerRequirementPage } from '../../applications/fleet/sections/agents/agent_requirements_page';
|
||||||
|
|
||||||
import {
|
import { DownloadStep, AgentPolicySelectionStep, AgentEnrollmentKeySelectionStep } from './steps';
|
||||||
DownloadStep,
|
|
||||||
AgentPolicySelectionStep,
|
|
||||||
AgentEnrollmentKeySelectionStep,
|
|
||||||
ViewDataStep,
|
|
||||||
} from './steps';
|
|
||||||
import type { BaseProps } from './types';
|
import type { BaseProps } from './types';
|
||||||
|
|
||||||
type Props = BaseProps;
|
type Props = BaseProps;
|
||||||
|
@ -61,7 +56,7 @@ const FleetServerMissingRequirements = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ManagedInstructions = React.memo<Props>(
|
export const ManagedInstructions = React.memo<Props>(
|
||||||
({ agentPolicy, agentPolicies, viewDataStepContent }) => {
|
({ agentPolicy, agentPolicies, viewDataStep }) => {
|
||||||
const fleetStatus = useFleetStatus();
|
const fleetStatus = useFleetStatus();
|
||||||
|
|
||||||
const [selectedApiKeyId, setSelectedAPIKeyId] = useState<string | undefined>();
|
const [selectedApiKeyId, setSelectedAPIKeyId] = useState<string | undefined>();
|
||||||
|
@ -118,8 +113,8 @@ export const ManagedInstructions = React.memo<Props>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewDataStepContent) {
|
if (viewDataStep) {
|
||||||
baseSteps.push(ViewDataStep(viewDataStepContent));
|
baseSteps.push({ 'data-test-subj': 'view-data-step', ...viewDataStep });
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseSteps;
|
return baseSteps;
|
||||||
|
@ -127,12 +122,12 @@ export const ManagedInstructions = React.memo<Props>(
|
||||||
agentPolicy,
|
agentPolicy,
|
||||||
selectedApiKeyId,
|
selectedApiKeyId,
|
||||||
setSelectedAPIKeyId,
|
setSelectedAPIKeyId,
|
||||||
viewDataStepContent,
|
|
||||||
agentPolicies,
|
agentPolicies,
|
||||||
apiKey.data,
|
apiKey.data,
|
||||||
fleetServerSteps,
|
fleetServerSteps,
|
||||||
isFleetServerPolicySelected,
|
isFleetServerPolicySelected,
|
||||||
settings.data?.item?.fleet_server_hosts,
|
settings.data?.item?.fleet_server_hosts,
|
||||||
|
viewDataStep,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -144,16 +144,3 @@ export const AgentEnrollmentKeySelectionStep = ({
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Send users to assets installed by the package in Kibana so they can
|
|
||||||
* view their data.
|
|
||||||
*/
|
|
||||||
export const ViewDataStep = (content: JSX.Element) => {
|
|
||||||
return {
|
|
||||||
title: i18n.translate('xpack.fleet.agentEnrollment.stepViewDataTitle', {
|
|
||||||
defaultMessage: 'View your data',
|
|
||||||
}),
|
|
||||||
children: content,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { EuiStepProps } from '@elastic/eui';
|
||||||
|
|
||||||
import type { AgentPolicy } from '../../types';
|
import type { AgentPolicy } from '../../types';
|
||||||
|
|
||||||
export interface BaseProps {
|
export interface BaseProps {
|
||||||
|
@ -24,5 +26,5 @@ export interface BaseProps {
|
||||||
* There is a step in the agent enrollment process that allows users to see the data from an integration represented in the UI
|
* There is a step in the agent enrollment process that allows users to see the data from an integration represented in the UI
|
||||||
* in some way. This is an area for consumers to render a button and text explaining how data can be viewed.
|
* in some way. This is an area for consumers to render a button and text explaining how data can be viewed.
|
||||||
*/
|
*/
|
||||||
viewDataStepContent?: JSX.Element;
|
viewDataStep?: EuiStepProps;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import type { FunctionComponent } from 'react';
|
||||||
|
import {
|
||||||
|
EuiAccordion,
|
||||||
|
EuiFlexGroup,
|
||||||
|
EuiFlexItem,
|
||||||
|
EuiText,
|
||||||
|
EuiNotificationBadge,
|
||||||
|
EuiSpacer,
|
||||||
|
EuiSplitPanel,
|
||||||
|
EuiLink,
|
||||||
|
EuiHorizontalRule,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
|
||||||
|
import { AssetTitleMap } from '../applications/integrations/sections/epm/constants';
|
||||||
|
import { useStartServices } from '../hooks';
|
||||||
|
import { RedirectAppLinks } from '../../../../../src/plugins/kibana_react/public';
|
||||||
|
|
||||||
|
export interface CustomAssetsAccordionProps {
|
||||||
|
views: Array<{
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
description: string;
|
||||||
|
}>;
|
||||||
|
initialIsOpen?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CustomAssetsAccordion: FunctionComponent<CustomAssetsAccordionProps> = ({
|
||||||
|
views,
|
||||||
|
initialIsOpen = false,
|
||||||
|
}) => {
|
||||||
|
const { application } = useStartServices();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiAccordion
|
||||||
|
initialIsOpen={initialIsOpen}
|
||||||
|
buttonContent={
|
||||||
|
<EuiFlexGroup justifyContent="center" alignItems="center" gutterSize="s" responsive={false}>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiText size="m">
|
||||||
|
<h3>{AssetTitleMap.view}</h3>
|
||||||
|
</EuiText>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiNotificationBadge color="subdued" size="m">
|
||||||
|
<h3>{views.length}</h3>
|
||||||
|
</EuiNotificationBadge>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
}
|
||||||
|
id="custom-assets"
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
<EuiSplitPanel.Outer hasBorder hasShadow={false}>
|
||||||
|
{views.map((view, index) => (
|
||||||
|
<>
|
||||||
|
<EuiSplitPanel.Inner grow={false} key={index}>
|
||||||
|
<EuiText size="m">
|
||||||
|
<p>
|
||||||
|
<RedirectAppLinks application={application}>
|
||||||
|
<EuiLink href={view.url}>{view.name}</EuiLink>
|
||||||
|
</RedirectAppLinks>
|
||||||
|
</p>
|
||||||
|
</EuiText>
|
||||||
|
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
<EuiText size="s" color="subdued">
|
||||||
|
<p>{view.description}</p>
|
||||||
|
</EuiText>
|
||||||
|
</EuiSplitPanel.Inner>
|
||||||
|
{index + 1 < views.length && <EuiHorizontalRule margin="none" />}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</EuiSplitPanel.Outer>
|
||||||
|
</>
|
||||||
|
</EuiAccordion>
|
||||||
|
);
|
||||||
|
};
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { EuiContextMenuItem, EuiPortal } from '@elastic/eui';
|
import { EuiContextMenuItem, EuiPortal } from '@elastic/eui';
|
||||||
|
import type { EuiStepProps } from '@elastic/eui';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
|
|
||||||
import type { AgentPolicy, PackagePolicy } from '../types';
|
import type { AgentPolicy, PackagePolicy } from '../types';
|
||||||
|
@ -21,8 +22,8 @@ import { PackagePolicyDeleteProvider } from './package_policy_delete_provider';
|
||||||
export const PackagePolicyActionsMenu: React.FunctionComponent<{
|
export const PackagePolicyActionsMenu: React.FunctionComponent<{
|
||||||
agentPolicy: AgentPolicy;
|
agentPolicy: AgentPolicy;
|
||||||
packagePolicy: PackagePolicy;
|
packagePolicy: PackagePolicy;
|
||||||
viewDataStepContent?: JSX.Element;
|
viewDataStep?: EuiStepProps;
|
||||||
}> = ({ agentPolicy, packagePolicy, viewDataStepContent }) => {
|
}> = ({ agentPolicy, packagePolicy, viewDataStep }) => {
|
||||||
const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false);
|
const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false);
|
||||||
const { getHref } = useLink();
|
const { getHref } = useLink();
|
||||||
const hasWriteCapabilities = useCapabilities().write;
|
const hasWriteCapabilities = useCapabilities().write;
|
||||||
|
@ -106,7 +107,7 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{
|
||||||
<EuiPortal>
|
<EuiPortal>
|
||||||
<AgentEnrollmentFlyout
|
<AgentEnrollmentFlyout
|
||||||
agentPolicy={agentPolicy}
|
agentPolicy={agentPolicy}
|
||||||
viewDataStepContent={viewDataStepContent}
|
viewDataStep={viewDataStep}
|
||||||
onClose={onEnrollmentFlyoutClose}
|
onClose={onEnrollmentFlyoutClose}
|
||||||
/>
|
/>
|
||||||
</EuiPortal>
|
</EuiPortal>
|
||||||
|
|
|
@ -24,3 +24,5 @@ export {
|
||||||
export * from './page_paths';
|
export * from './page_paths';
|
||||||
|
|
||||||
export const INDEX_NAME = '.kibana';
|
export const INDEX_NAME = '.kibana';
|
||||||
|
|
||||||
|
export const CUSTOM_LOGS_INTEGRATION_NAME = 'log';
|
||||||
|
|
31
x-pack/plugins/fleet/public/custom_logs_assets_extension.tsx
Normal file
31
x-pack/plugins/fleet/public/custom_logs_assets_extension.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
|
import { CustomAssetsAccordion } from './components/custom_assets_accordion';
|
||||||
|
import type { CustomAssetsAccordionProps } from './components/custom_assets_accordion';
|
||||||
|
import { useStartServices } from './hooks';
|
||||||
|
import type { PackageAssetsComponent } from './types';
|
||||||
|
|
||||||
|
export const CustomLogsAssetsExtension: PackageAssetsComponent = () => {
|
||||||
|
const { http } = useStartServices();
|
||||||
|
const logStreamUrl = http.basePath.prepend('/app/logs/stream');
|
||||||
|
|
||||||
|
const views: CustomAssetsAccordionProps['views'] = [
|
||||||
|
{
|
||||||
|
name: i18n.translate('xpack.fleet.assets.customLogs.name', { defaultMessage: 'Logs' }),
|
||||||
|
url: logStreamUrl,
|
||||||
|
description: i18n.translate('xpack.fleet.assets.customLogs.description', {
|
||||||
|
defaultMessage: 'View Custom logs data in Logs app',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return <CustomAssetsAccordion views={views} initialIsOpen />;
|
||||||
|
};
|
|
@ -20,7 +20,7 @@ type NarrowExtensionPoint<V extends UIExtensionPoint['view'], A = UIExtensionPoi
|
||||||
export const useUIExtension = <V extends UIExtensionPoint['view'] = UIExtensionPoint['view']>(
|
export const useUIExtension = <V extends UIExtensionPoint['view'] = UIExtensionPoint['view']>(
|
||||||
packageName: UIExtensionPoint['package'],
|
packageName: UIExtensionPoint['package'],
|
||||||
view: V
|
view: V
|
||||||
): NarrowExtensionPoint<V>['component'] | undefined => {
|
): NarrowExtensionPoint<V> | undefined => {
|
||||||
const registeredExtensions = useContext(UIExtensionsContext);
|
const registeredExtensions = useContext(UIExtensionsContext);
|
||||||
|
|
||||||
if (!registeredExtensions) {
|
if (!registeredExtensions) {
|
||||||
|
@ -32,6 +32,6 @@ export const useUIExtension = <V extends UIExtensionPoint['view'] = UIExtensionP
|
||||||
if (extension) {
|
if (extension) {
|
||||||
// FIXME:PT Revisit ignore below and see if TS error can be addressed
|
// FIXME:PT Revisit ignore below and see if TS error can be addressed
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return extension.component;
|
return extension;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,3 +21,7 @@ export * from './types/ui_extensions';
|
||||||
|
|
||||||
export { pagePathGetters } from './constants';
|
export { pagePathGetters } from './constants';
|
||||||
export { pkgKeyFromPackageInfo } from './services';
|
export { pkgKeyFromPackageInfo } from './services';
|
||||||
|
export {
|
||||||
|
CustomAssetsAccordion,
|
||||||
|
CustomAssetsAccordionProps,
|
||||||
|
} from './components/custom_assets_accordion';
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { lazy } from 'react';
|
||||||
|
|
||||||
|
import type { PackageAssetsComponent } from './types';
|
||||||
|
import { CustomLogsAssetsExtension } from './custom_logs_assets_extension';
|
||||||
|
|
||||||
|
export const LazyCustomLogsAssetsExtension = lazy<PackageAssetsComponent>(async () => {
|
||||||
|
return {
|
||||||
|
default: CustomLogsAssetsExtension,
|
||||||
|
};
|
||||||
|
});
|
|
@ -32,7 +32,7 @@ import type { CheckPermissionsResponse, PostIngestSetupResponse } from '../commo
|
||||||
|
|
||||||
import type { FleetConfigType } from '../common/types';
|
import type { FleetConfigType } from '../common/types';
|
||||||
|
|
||||||
import { FLEET_BASE_PATH } from './constants';
|
import { CUSTOM_LOGS_INTEGRATION_NAME, FLEET_BASE_PATH } from './constants';
|
||||||
import { licenseService } from './hooks';
|
import { licenseService } from './hooks';
|
||||||
import { setHttpClient } from './hooks/use_request';
|
import { setHttpClient } from './hooks/use_request';
|
||||||
import { createPackageSearchProvider } from './search_provider';
|
import { createPackageSearchProvider } from './search_provider';
|
||||||
|
@ -43,6 +43,7 @@ import {
|
||||||
} from './components/home_integration';
|
} from './components/home_integration';
|
||||||
import { createExtensionRegistrationCallback } from './services/ui_extensions';
|
import { createExtensionRegistrationCallback } from './services/ui_extensions';
|
||||||
import type { UIExtensionRegistrationCallback, UIExtensionsStorage } from './types';
|
import type { UIExtensionRegistrationCallback, UIExtensionsStorage } from './types';
|
||||||
|
import { LazyCustomLogsAssetsExtension } from './lazy_custom_logs_assets_extension';
|
||||||
|
|
||||||
export { FleetConfigType } from '../common/types';
|
export { FleetConfigType } from '../common/types';
|
||||||
|
|
||||||
|
@ -204,6 +205,13 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep
|
||||||
|
|
||||||
public start(core: CoreStart): FleetStart {
|
public start(core: CoreStart): FleetStart {
|
||||||
let successPromise: ReturnType<FleetStart['isInitialized']>;
|
let successPromise: ReturnType<FleetStart['isInitialized']>;
|
||||||
|
const registerExtension = createExtensionRegistrationCallback(this.extensions);
|
||||||
|
|
||||||
|
registerExtension({
|
||||||
|
package: CUSTOM_LOGS_INTEGRATION_NAME,
|
||||||
|
view: 'package-detail-assets',
|
||||||
|
Component: LazyCustomLogsAssetsExtension,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isInitialized: () => {
|
isInitialized: () => {
|
||||||
|
@ -229,8 +237,7 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep
|
||||||
|
|
||||||
return successPromise;
|
return successPromise;
|
||||||
},
|
},
|
||||||
|
registerExtension,
|
||||||
registerExtension: createExtensionRegistrationCallback(this.extensions),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,13 +36,13 @@ describe('UI Extension services', () => {
|
||||||
register({
|
register({
|
||||||
view: 'package-policy-edit',
|
view: 'package-policy-edit',
|
||||||
package: 'endpoint',
|
package: 'endpoint',
|
||||||
component: LazyCustomView,
|
Component: LazyCustomView,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(storage.endpoint['package-policy-edit']).toEqual({
|
expect(storage.endpoint['package-policy-edit']).toEqual({
|
||||||
view: 'package-policy-edit',
|
view: 'package-policy-edit',
|
||||||
package: 'endpoint',
|
package: 'endpoint',
|
||||||
component: LazyCustomView,
|
Component: LazyCustomView,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -57,21 +57,21 @@ describe('UI Extension services', () => {
|
||||||
register({
|
register({
|
||||||
view: 'package-policy-edit',
|
view: 'package-policy-edit',
|
||||||
package: 'endpoint',
|
package: 'endpoint',
|
||||||
component: LazyCustomView,
|
Component: LazyCustomView,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
register({
|
register({
|
||||||
view: 'package-policy-edit',
|
view: 'package-policy-edit',
|
||||||
package: 'endpoint',
|
package: 'endpoint',
|
||||||
component: LazyCustomView2,
|
Component: LazyCustomView2,
|
||||||
});
|
});
|
||||||
}).toThrow();
|
}).toThrow();
|
||||||
|
|
||||||
expect(storage.endpoint['package-policy-edit']).toEqual({
|
expect(storage.endpoint['package-policy-edit']).toEqual({
|
||||||
view: 'package-policy-edit',
|
view: 'package-policy-edit',
|
||||||
package: 'endpoint',
|
package: 'endpoint',
|
||||||
component: LazyCustomView,
|
Component: LazyCustomView,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { EuiStepProps } from '@elastic/eui';
|
||||||
import type { ComponentType, LazyExoticComponent } from 'react';
|
import type { ComponentType, LazyExoticComponent } from 'react';
|
||||||
|
|
||||||
import type { NewPackagePolicy, PackageInfo, PackagePolicy } from './index';
|
import type { NewPackagePolicy, PackageInfo, PackagePolicy } from './index';
|
||||||
|
@ -48,7 +49,7 @@ export interface PackagePolicyEditExtensionComponentProps {
|
||||||
export interface PackagePolicyEditExtension {
|
export interface PackagePolicyEditExtension {
|
||||||
package: string;
|
package: string;
|
||||||
view: 'package-policy-edit';
|
view: 'package-policy-edit';
|
||||||
component: LazyExoticComponent<PackagePolicyEditExtensionComponent>;
|
Component: LazyExoticComponent<PackagePolicyEditExtensionComponent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,7 +77,7 @@ export interface PackagePolicyCreateExtensionComponentProps {
|
||||||
export interface PackagePolicyCreateExtension {
|
export interface PackagePolicyCreateExtension {
|
||||||
package: string;
|
package: string;
|
||||||
view: 'package-policy-create';
|
view: 'package-policy-create';
|
||||||
component: LazyExoticComponent<PackagePolicyCreateExtensionComponent>;
|
Component: LazyExoticComponent<PackagePolicyCreateExtensionComponent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,11 +95,32 @@ export interface PackageCustomExtensionComponentProps {
|
||||||
export interface PackageCustomExtension {
|
export interface PackageCustomExtension {
|
||||||
package: string;
|
package: string;
|
||||||
view: 'package-detail-custom';
|
view: 'package-detail-custom';
|
||||||
component: LazyExoticComponent<PackageCustomExtensionComponent>;
|
Component: LazyExoticComponent<PackageCustomExtensionComponent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI Component Extension for displaying custom views under the Assets tab for a given Integration
|
||||||
|
*/
|
||||||
|
export type PackageAssetsComponent = ComponentType<{}>;
|
||||||
|
|
||||||
|
/** Extension point registration contract for Integration details Assets view */
|
||||||
|
export interface PackageAssetsExtension {
|
||||||
|
package: string;
|
||||||
|
view: 'package-detail-assets';
|
||||||
|
Component: LazyExoticComponent<PackageAssetsComponent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentEnrollmentFlyoutFinalStepExtension {
|
||||||
|
package: string;
|
||||||
|
view: 'agent-enrollment-flyout';
|
||||||
|
title: EuiStepProps['title'];
|
||||||
|
Component: ComponentType<{}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fleet UI Extension Point */
|
/** Fleet UI Extension Point */
|
||||||
export type UIExtensionPoint =
|
export type UIExtensionPoint =
|
||||||
| PackagePolicyEditExtension
|
| PackagePolicyEditExtension
|
||||||
| PackageCustomExtension
|
| PackageCustomExtension
|
||||||
| PackagePolicyCreateExtension;
|
| PackagePolicyCreateExtension
|
||||||
|
| PackageAssetsExtension
|
||||||
|
| AgentEnrollmentFlyoutFinalStepExtension;
|
||||||
|
|
|
@ -58,7 +58,7 @@ export function toggleOsqueryPlugin(
|
||||||
registerExtension({
|
registerExtension({
|
||||||
package: OSQUERY_INTEGRATION_NAME,
|
package: OSQUERY_INTEGRATION_NAME,
|
||||||
view: 'package-detail-custom',
|
view: 'package-detail-custom',
|
||||||
component: LazyOsqueryManagedCustomButtonExtension,
|
Component: LazyOsqueryManagedCustomButtonExtension,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,13 +146,13 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
||||||
registerExtension({
|
registerExtension({
|
||||||
package: OSQUERY_INTEGRATION_NAME,
|
package: OSQUERY_INTEGRATION_NAME,
|
||||||
view: 'package-policy-create',
|
view: 'package-policy-create',
|
||||||
component: LazyOsqueryManagedPolicyCreateImportExtension,
|
Component: LazyOsqueryManagedPolicyCreateImportExtension,
|
||||||
});
|
});
|
||||||
|
|
||||||
registerExtension({
|
registerExtension({
|
||||||
package: OSQUERY_INTEGRATION_NAME,
|
package: OSQUERY_INTEGRATION_NAME,
|
||||||
view: 'package-policy-edit',
|
view: 'package-policy-edit',
|
||||||
component: LazyOsqueryManagedPolicyEditExtension,
|
Component: LazyOsqueryManagedPolicyEditExtension,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
|
import { useKibana } from '../../../../../common/lib/kibana';
|
||||||
|
import { APP_PATH } from '../../../../../../common/constants';
|
||||||
|
import {
|
||||||
|
CustomAssetsAccordionProps,
|
||||||
|
CustomAssetsAccordion,
|
||||||
|
PackageAssetsComponent,
|
||||||
|
} from '../../../../../../../fleet/public';
|
||||||
|
|
||||||
|
export const EndpointCustomAssetsExtension: PackageAssetsComponent = () => {
|
||||||
|
const { http } = useKibana().services;
|
||||||
|
const views: CustomAssetsAccordionProps['views'] = [
|
||||||
|
{
|
||||||
|
name: i18n.translate('xpack.securitySolution.fleetIntegration.assets.name', {
|
||||||
|
defaultMessage: 'Hosts',
|
||||||
|
}),
|
||||||
|
url: http.basePath.prepend(`${APP_PATH}/administration/endpoints`),
|
||||||
|
description: i18n.translate('xpack.securitySolution.fleetIntegration.assets.description', {
|
||||||
|
defaultMessage: 'View endpoints in Security app',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return <CustomAssetsAccordion views={views} initialIsOpen />;
|
||||||
|
};
|
|
@ -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 { lazy } from 'react';
|
||||||
|
|
||||||
|
export const LazyEndpointCustomAssetsExtension = lazy(async () => {
|
||||||
|
const { EndpointCustomAssetsExtension } = await import('./endpoint_custom_assets_extension');
|
||||||
|
|
||||||
|
return {
|
||||||
|
default: EndpointCustomAssetsExtension,
|
||||||
|
};
|
||||||
|
});
|
|
@ -60,6 +60,7 @@ import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/vie
|
||||||
import { getLazyEndpointPackageCustomExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension';
|
import { getLazyEndpointPackageCustomExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension';
|
||||||
import { parseExperimentalConfigValue } from '../common/experimental_features';
|
import { parseExperimentalConfigValue } from '../common/experimental_features';
|
||||||
import type { TimelineState } from '../../timelines/public';
|
import type { TimelineState } from '../../timelines/public';
|
||||||
|
import { LazyEndpointCustomAssetsExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_custom_assets_extension';
|
||||||
|
|
||||||
export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, StartPlugins> {
|
export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, StartPlugins> {
|
||||||
private kibanaVersion: string;
|
private kibanaVersion: string;
|
||||||
|
@ -199,19 +200,25 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
||||||
registerExtension({
|
registerExtension({
|
||||||
package: 'endpoint',
|
package: 'endpoint',
|
||||||
view: 'package-policy-edit',
|
view: 'package-policy-edit',
|
||||||
component: getLazyEndpointPolicyEditExtension(core, plugins),
|
Component: getLazyEndpointPolicyEditExtension(core, plugins),
|
||||||
});
|
});
|
||||||
|
|
||||||
registerExtension({
|
registerExtension({
|
||||||
package: 'endpoint',
|
package: 'endpoint',
|
||||||
view: 'package-policy-create',
|
view: 'package-policy-create',
|
||||||
component: LazyEndpointPolicyCreateExtension,
|
Component: LazyEndpointPolicyCreateExtension,
|
||||||
});
|
});
|
||||||
|
|
||||||
registerExtension({
|
registerExtension({
|
||||||
package: 'endpoint',
|
package: 'endpoint',
|
||||||
view: 'package-detail-custom',
|
view: 'package-detail-custom',
|
||||||
component: getLazyEndpointPackageCustomExtension(core, plugins),
|
Component: getLazyEndpointPackageCustomExtension(core, plugins),
|
||||||
|
});
|
||||||
|
|
||||||
|
registerExtension({
|
||||||
|
package: 'endpoint',
|
||||||
|
view: 'package-detail-assets',
|
||||||
|
Component: LazyEndpointCustomAssetsExtension,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
licenseService.start(plugins.licensing.license$);
|
licenseService.start(plugins.licensing.license$);
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"server": true,
|
"server": true,
|
||||||
"ui": true,
|
"ui": true,
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"requiredBundles": ["observability", "kibanaReact", "kibanaUtils", "home", "data", "ml"],
|
"requiredBundles": ["observability", "kibanaReact", "kibanaUtils", "home", "data", "ml", "fleet"],
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "Uptime",
|
"name": "Uptime",
|
||||||
"githubTeam": "uptime"
|
"githubTeam": "uptime"
|
||||||
|
|
|
@ -42,6 +42,7 @@ import {
|
||||||
LazySyntheticsPolicyCreateExtension,
|
LazySyntheticsPolicyCreateExtension,
|
||||||
LazySyntheticsPolicyEditExtension,
|
LazySyntheticsPolicyEditExtension,
|
||||||
} from '../components/fleet_package';
|
} from '../components/fleet_package';
|
||||||
|
import { LazySyntheticsCustomAssetsExtension } from '../components/fleet_package/lazy_synthetics_custom_assets_extension';
|
||||||
|
|
||||||
export interface ClientPluginsSetup {
|
export interface ClientPluginsSetup {
|
||||||
data: DataPublicPluginSetup;
|
data: DataPublicPluginSetup;
|
||||||
|
@ -196,13 +197,19 @@ export class UptimePlugin
|
||||||
registerExtension({
|
registerExtension({
|
||||||
package: 'synthetics',
|
package: 'synthetics',
|
||||||
view: 'package-policy-create',
|
view: 'package-policy-create',
|
||||||
component: LazySyntheticsPolicyCreateExtension,
|
Component: LazySyntheticsPolicyCreateExtension,
|
||||||
});
|
});
|
||||||
|
|
||||||
registerExtension({
|
registerExtension({
|
||||||
package: 'synthetics',
|
package: 'synthetics',
|
||||||
view: 'package-policy-edit',
|
view: 'package-policy-edit',
|
||||||
component: LazySyntheticsPolicyEditExtension,
|
Component: LazySyntheticsPolicyEditExtension,
|
||||||
|
});
|
||||||
|
|
||||||
|
registerExtension({
|
||||||
|
package: 'synthetics',
|
||||||
|
view: 'package-detail-assets',
|
||||||
|
Component: LazySyntheticsCustomAssetsExtension,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { lazy } from 'react';
|
||||||
|
|
||||||
|
export const LazySyntheticsCustomAssetsExtension = lazy(async () => {
|
||||||
|
const { SyntheticsCustomAssetsExtension } = await import('./synthetics_custom_assets_extension');
|
||||||
|
|
||||||
|
return {
|
||||||
|
default: SyntheticsCustomAssetsExtension,
|
||||||
|
};
|
||||||
|
});
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
|
import {
|
||||||
|
PackageAssetsComponent,
|
||||||
|
CustomAssetsAccordionProps,
|
||||||
|
CustomAssetsAccordion,
|
||||||
|
} from '../../../../fleet/public';
|
||||||
|
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||||
|
import { ClientPluginsStart } from '../../apps/plugin';
|
||||||
|
import { PLUGIN } from '../../../common/constants/plugin';
|
||||||
|
|
||||||
|
export const SyntheticsCustomAssetsExtension: PackageAssetsComponent = () => {
|
||||||
|
const { http } = useKibana<ClientPluginsStart>().services;
|
||||||
|
const views: CustomAssetsAccordionProps['views'] = [
|
||||||
|
{
|
||||||
|
name: i18n.translate('xpack.uptime.fleetIntegration.assets.name', {
|
||||||
|
defaultMessage: 'Monitors',
|
||||||
|
}),
|
||||||
|
url: http?.basePath.prepend(`/app/${PLUGIN.ID}`) ?? '',
|
||||||
|
description: i18n.translate('xpack.uptime.fleetIntegration.assets.description', {
|
||||||
|
defaultMessage: 'View monitors in Uptime',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return <CustomAssetsAccordion views={views} initialIsOpen />;
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue