mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Fleet] IngestManager Plugin interface for registering UI extensions (#82783)
* Expose `registerExtension()` interface on `Plugin#start` * Refactor use of `CustomConfigurePackagePolicy` to the new registerExtension approach * Refactor to always show registered ui extension (even if Integration has configuration options)
This commit is contained in:
parent
ab72206da3
commit
1babb5f6bf
20 changed files with 478 additions and 125 deletions
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { memo, ReactNode, Suspense } from 'react';
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import { Loading } from './loading';
|
||||
|
||||
export const ExtensionWrapper = memo<{ children: ReactNode }>(({ children }) => {
|
||||
return (
|
||||
<EuiErrorBoundary>
|
||||
<Suspense fallback={<Loading />}>{children}</Suspense>
|
||||
</EuiErrorBoundary>
|
||||
);
|
||||
});
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { UIExtensionPoint, UIExtensionsStorage } from '../types';
|
||||
|
||||
export const UIExtensionsContext = React.createContext<UIExtensionsStorage>({});
|
||||
|
||||
type NarrowExtensionPoint<V extends UIExtensionPoint['view'], A = UIExtensionPoint> = A extends {
|
||||
view: V;
|
||||
}
|
||||
? A
|
||||
: never;
|
||||
|
||||
export const useUIExtension = <V extends UIExtensionPoint['view'] = UIExtensionPoint['view']>(
|
||||
packageName: UIExtensionPoint['package'],
|
||||
view: V
|
||||
): NarrowExtensionPoint<V>['component'] | undefined => {
|
||||
const registeredExtensions = useContext(UIExtensionsContext);
|
||||
|
||||
if (!registeredExtensions) {
|
||||
throw new Error('useUIExtension called outside of UIExtensionsContext');
|
||||
}
|
||||
|
||||
const extension = registeredExtensions?.[packageName]?.[view];
|
||||
|
||||
if (extension) {
|
||||
// FIXME:PT Revisit ignore below and see if TS error can be addressed
|
||||
// @ts-ignore
|
||||
return extension.component;
|
||||
}
|
||||
};
|
|
@ -36,6 +36,8 @@ import {
|
|||
import { PackageInstallProvider } from './sections/epm/hooks';
|
||||
import { FleetStatusProvider, useBreadcrumbs } from './hooks';
|
||||
import { IntraAppStateProvider } from './hooks/use_intra_app_state';
|
||||
import { UIExtensionsStorage } from './types';
|
||||
import { UIExtensionsContext } from './hooks/use_ui_extension';
|
||||
|
||||
export interface ProtectedRouteProps extends RouteProps {
|
||||
isAllowed?: boolean;
|
||||
|
@ -235,6 +237,7 @@ const IngestManagerApp = ({
|
|||
config,
|
||||
history,
|
||||
kibanaVersion,
|
||||
extensions,
|
||||
}: {
|
||||
basepath: string;
|
||||
coreStart: CoreStart;
|
||||
|
@ -243,6 +246,7 @@ const IngestManagerApp = ({
|
|||
config: IngestManagerConfigType;
|
||||
history: AppMountParameters['history'];
|
||||
kibanaVersion: string;
|
||||
extensions: UIExtensionsStorage;
|
||||
}) => {
|
||||
const isDarkMode = useObservable<boolean>(coreStart.uiSettings.get$('theme:darkMode'));
|
||||
return (
|
||||
|
@ -252,7 +256,9 @@ const IngestManagerApp = ({
|
|||
<ConfigContext.Provider value={config}>
|
||||
<KibanaVersionContext.Provider value={kibanaVersion}>
|
||||
<EuiThemeProvider darkMode={isDarkMode}>
|
||||
<IngestManagerRoutes history={history} basepath={basepath} />
|
||||
<UIExtensionsContext.Provider value={extensions}>
|
||||
<IngestManagerRoutes history={history} basepath={basepath} />
|
||||
</UIExtensionsContext.Provider>
|
||||
</EuiThemeProvider>
|
||||
</KibanaVersionContext.Provider>
|
||||
</ConfigContext.Provider>
|
||||
|
@ -268,7 +274,8 @@ export function renderApp(
|
|||
setupDeps: IngestManagerSetupDeps,
|
||||
startDeps: IngestManagerStartDeps,
|
||||
config: IngestManagerConfigType,
|
||||
kibanaVersion: string
|
||||
kibanaVersion: string,
|
||||
extensions: UIExtensionsStorage
|
||||
) {
|
||||
ReactDOM.render(
|
||||
<IngestManagerApp
|
||||
|
@ -279,6 +286,7 @@ export function renderApp(
|
|||
config={config}
|
||||
history={history}
|
||||
kibanaVersion={kibanaVersion}
|
||||
extensions={extensions}
|
||||
/>,
|
||||
element
|
||||
);
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiEmptyPrompt, EuiText } from '@elastic/eui';
|
||||
import { NewPackagePolicy } from '../../../../types';
|
||||
import { CreatePackagePolicyFrom } from '../types';
|
||||
|
||||
export interface CustomConfigurePackagePolicyProps {
|
||||
packageName: string;
|
||||
from: CreatePackagePolicyFrom;
|
||||
packagePolicy: NewPackagePolicy;
|
||||
packagePolicyId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom content type that external plugins can provide to Ingest's
|
||||
* package policy UI.
|
||||
*/
|
||||
export type CustomConfigurePackagePolicyContent = React.FC<CustomConfigurePackagePolicyProps>;
|
||||
|
||||
type AllowedPackageKey = 'endpoint';
|
||||
const PackagePolicyMapping: {
|
||||
[key: string]: CustomConfigurePackagePolicyContent;
|
||||
} = {};
|
||||
|
||||
/**
|
||||
* Plugins can call this function from the start lifecycle to
|
||||
* register a custom component in the Ingest package policy.
|
||||
*/
|
||||
export function registerPackagePolicyComponent(
|
||||
key: AllowedPackageKey,
|
||||
value: CustomConfigurePackagePolicyContent
|
||||
) {
|
||||
PackagePolicyMapping[key] = value;
|
||||
}
|
||||
|
||||
const EmptyPackagePolicy: CustomConfigurePackagePolicyContent = () => (
|
||||
<EuiEmptyPrompt
|
||||
iconType="checkInCircleFilled"
|
||||
iconColor="secondary"
|
||||
body={
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.noPolicyOptionsMessage"
|
||||
defaultMessage="Nothing to configure"
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
export const CustomPackagePolicy = (props: CustomConfigurePackagePolicyProps) => {
|
||||
const CustomPackagePolicyContent = PackagePolicyMapping[props.packageName] || EmptyPackagePolicy;
|
||||
return <CustomPackagePolicyContent {...props} />;
|
||||
};
|
|
@ -6,4 +6,3 @@
|
|||
export { CreatePackagePolicyPageLayout } from './layout';
|
||||
export { PackagePolicyInputPanel } from './package_policy_input_panel';
|
||||
export { PackagePolicyInputVarField } from './package_policy_input_var_field';
|
||||
export { CustomPackagePolicy } from './custom_package_policy';
|
||||
|
|
|
@ -46,6 +46,9 @@ import { StepSelectAgentPolicy } from './step_select_agent_policy';
|
|||
import { StepConfigurePackagePolicy } from './step_configure_package';
|
||||
import { StepDefinePackagePolicy } from './step_define_package_policy';
|
||||
import { useIntraAppState } from '../../../hooks/use_intra_app_state';
|
||||
import { useUIExtension } from '../../../hooks/use_ui_extension';
|
||||
import { ExtensionWrapper } from '../../../components/extension_wrapper';
|
||||
import { PackagePolicyEditExtensionComponentProps } from '../../../types';
|
||||
|
||||
const StepsWithLessPadding = styled(EuiSteps)`
|
||||
.euiStep__content {
|
||||
|
@ -191,6 +194,21 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
[packagePolicy, updatePackagePolicyValidation]
|
||||
);
|
||||
|
||||
const handleExtensionViewOnChange = useCallback<
|
||||
PackagePolicyEditExtensionComponentProps['onChange']
|
||||
>(
|
||||
({ isValid, updatedPolicy }) => {
|
||||
updatePackagePolicy(updatedPolicy);
|
||||
setFormState((prevState) => {
|
||||
if (prevState === 'VALID' && !isValid) {
|
||||
return 'INVALID';
|
||||
}
|
||||
return prevState;
|
||||
});
|
||||
},
|
||||
[updatePackagePolicy]
|
||||
);
|
||||
|
||||
// Cancel path
|
||||
const cancelUrl = useMemo(() => {
|
||||
if (routeState && routeState.onCancelUrl) {
|
||||
|
@ -287,6 +305,8 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
[pkgkey, updatePackageInfo, agentPolicy, updateAgentPolicy]
|
||||
);
|
||||
|
||||
const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create');
|
||||
|
||||
const stepSelectPackage = useMemo(
|
||||
() => (
|
||||
<StepSelectPackage
|
||||
|
@ -320,18 +340,26 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
validationResults={validationResults!}
|
||||
submitAttempted={formState === 'INVALID'}
|
||||
/>
|
||||
{/* If an Agent Policy and a package has been selected, then show UI extension (if any) */}
|
||||
{packagePolicy.policy_id && packagePolicy.package?.name && ExtensionView && (
|
||||
<ExtensionWrapper>
|
||||
<ExtensionView newPolicy={packagePolicy} onChange={handleExtensionViewOnChange} />
|
||||
</ExtensionWrapper>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div />
|
||||
),
|
||||
[
|
||||
agentPolicy,
|
||||
formState,
|
||||
isLoadingSecondStep,
|
||||
packagePolicy,
|
||||
agentPolicy,
|
||||
packageInfo,
|
||||
packagePolicy,
|
||||
updatePackagePolicy,
|
||||
validationResults,
|
||||
formState,
|
||||
ExtensionView,
|
||||
handleExtensionViewOnChange,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -4,7 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import {
|
||||
EuiHorizontalRule,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiEmptyPrompt,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
PackageInfo,
|
||||
RegistryStream,
|
||||
|
@ -13,8 +20,9 @@ import {
|
|||
} from '../../../types';
|
||||
import { Loading } from '../../../components';
|
||||
import { PackagePolicyValidationResults } from './services';
|
||||
import { PackagePolicyInputPanel, CustomPackagePolicy } from './components';
|
||||
import { PackagePolicyInputPanel } from './components';
|
||||
import { CreatePackagePolicyFrom } from './types';
|
||||
import { useUIExtension } from '../../../hooks/use_ui_extension';
|
||||
|
||||
const findStreamsForInputType = (
|
||||
inputType: string,
|
||||
|
@ -55,6 +63,12 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{
|
|||
validationResults,
|
||||
submitAttempted,
|
||||
}) => {
|
||||
const hasUiExtension =
|
||||
useUIExtension(
|
||||
packageInfo.name,
|
||||
from === 'edit' ? 'package-policy-edit' : 'package-policy-create'
|
||||
) !== undefined;
|
||||
|
||||
// Configure inputs (and their streams)
|
||||
// Assume packages only export one config template for now
|
||||
const renderConfigureInputs = () =>
|
||||
|
@ -98,12 +112,20 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{
|
|||
})}
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
) : (
|
||||
<CustomPackagePolicy
|
||||
from={from}
|
||||
packageName={packageInfo.name}
|
||||
packagePolicy={packagePolicy}
|
||||
packagePolicyId={packagePolicyId}
|
||||
) : hasUiExtension ? null : (
|
||||
<EuiEmptyPrompt
|
||||
iconType="checkInCircleFilled"
|
||||
iconColor="secondary"
|
||||
body={
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.noPolicyOptionsMessage"
|
||||
defaultMessage="Nothing to configure"
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -41,6 +41,10 @@ import {
|
|||
} from '../create_package_policy_page/types';
|
||||
import { StepConfigurePackagePolicy } from '../create_package_policy_page/step_configure_package';
|
||||
import { StepDefinePackagePolicy } from '../create_package_policy_page/step_define_package_policy';
|
||||
import { useUIExtension } from '../../../hooks/use_ui_extension';
|
||||
import { ExtensionWrapper } from '../../../components/extension_wrapper';
|
||||
import { GetOnePackagePolicyResponse } from '../../../../../../common/types/rest_spec';
|
||||
import { PackagePolicyEditExtensionComponentProps } from '../../../types';
|
||||
|
||||
export const EditPackagePolicyPage: React.FunctionComponent = () => {
|
||||
const { notifications } = useCore();
|
||||
|
@ -68,6 +72,9 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
|
|||
inputs: [],
|
||||
version: '',
|
||||
});
|
||||
const [originalPackagePolicy, setOriginalPackagePolicy] = useState<
|
||||
GetOnePackagePolicyResponse['item']
|
||||
>();
|
||||
|
||||
// Retrieve agent policy, package, and package policy info
|
||||
useEffect(() => {
|
||||
|
@ -83,6 +90,8 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
|
|||
setAgentPolicy(agentPolicyData.item);
|
||||
}
|
||||
if (packagePolicyData?.item) {
|
||||
setOriginalPackagePolicy(packagePolicyData.item);
|
||||
|
||||
const {
|
||||
id,
|
||||
revision,
|
||||
|
@ -189,6 +198,21 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
|
|||
[packagePolicy, updatePackagePolicyValidation]
|
||||
);
|
||||
|
||||
const handleExtensionViewOnChange = useCallback<
|
||||
PackagePolicyEditExtensionComponentProps['onChange']
|
||||
>(
|
||||
({ isValid, updatedPolicy }) => {
|
||||
updatePackagePolicy(updatedPolicy);
|
||||
setFormState((prevState) => {
|
||||
if (prevState === 'VALID' && !isValid) {
|
||||
return 'INVALID';
|
||||
}
|
||||
return prevState;
|
||||
});
|
||||
},
|
||||
[updatePackagePolicy]
|
||||
);
|
||||
|
||||
// Cancel url
|
||||
const cancelUrl = getHref('policy_details', { policyId });
|
||||
|
||||
|
@ -267,6 +291,8 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
|
|||
packageInfo,
|
||||
};
|
||||
|
||||
const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-edit');
|
||||
|
||||
const configurePackage = useMemo(
|
||||
() =>
|
||||
agentPolicy && packageInfo ? (
|
||||
|
@ -288,16 +314,32 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
|
|||
validationResults={validationResults!}
|
||||
submitAttempted={formState === 'INVALID'}
|
||||
/>
|
||||
|
||||
{packagePolicy.policy_id &&
|
||||
packagePolicy.package?.name &&
|
||||
originalPackagePolicy &&
|
||||
ExtensionView && (
|
||||
<ExtensionWrapper>
|
||||
<ExtensionView
|
||||
policy={originalPackagePolicy}
|
||||
newPolicy={packagePolicy}
|
||||
onChange={handleExtensionViewOnChange}
|
||||
/>
|
||||
</ExtensionWrapper>
|
||||
)}
|
||||
</>
|
||||
) : null,
|
||||
[
|
||||
agentPolicy,
|
||||
formState,
|
||||
packagePolicy,
|
||||
packagePolicyId,
|
||||
packageInfo,
|
||||
packagePolicy,
|
||||
updatePackagePolicy,
|
||||
validationResults,
|
||||
packagePolicyId,
|
||||
formState,
|
||||
originalPackagePolicy,
|
||||
ExtensionView,
|
||||
handleExtensionViewOnChange,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
|
||||
import {
|
||||
PackagePolicyEditExtensionComponent,
|
||||
UIExtensionRegistrationCallback,
|
||||
UIExtensionsStorage,
|
||||
} from '../types';
|
||||
import { createExtensionRegistrationCallback } from './ui_extensions';
|
||||
|
||||
describe('UI Extension services', () => {
|
||||
describe('When using createExtensionRegistrationCallback factory', () => {
|
||||
let storage: UIExtensionsStorage;
|
||||
let register: UIExtensionRegistrationCallback;
|
||||
|
||||
beforeEach(() => {
|
||||
storage = {};
|
||||
register = createExtensionRegistrationCallback(storage);
|
||||
});
|
||||
|
||||
it('should return a function', () => {
|
||||
expect(register).toBeInstanceOf(Function);
|
||||
});
|
||||
|
||||
it('should store an extension points', () => {
|
||||
const LazyCustomView = lazy<PackagePolicyEditExtensionComponent>(async () => {
|
||||
return { default: ((() => {}) as unknown) as PackagePolicyEditExtensionComponent };
|
||||
});
|
||||
register({
|
||||
view: 'package-policy-edit',
|
||||
package: 'endpoint',
|
||||
component: LazyCustomView,
|
||||
});
|
||||
|
||||
expect(storage.endpoint['package-policy-edit']).toEqual({
|
||||
view: 'package-policy-edit',
|
||||
package: 'endpoint',
|
||||
component: LazyCustomView,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if extension point has already registered', () => {
|
||||
const LazyCustomView = lazy<PackagePolicyEditExtensionComponent>(async () => {
|
||||
return { default: ((() => {}) as unknown) as PackagePolicyEditExtensionComponent };
|
||||
});
|
||||
const LazyCustomView2 = lazy<PackagePolicyEditExtensionComponent>(async () => {
|
||||
return { default: ((() => {}) as unknown) as PackagePolicyEditExtensionComponent };
|
||||
});
|
||||
|
||||
register({
|
||||
view: 'package-policy-edit',
|
||||
package: 'endpoint',
|
||||
component: LazyCustomView,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
register({
|
||||
view: 'package-policy-edit',
|
||||
package: 'endpoint',
|
||||
component: LazyCustomView2,
|
||||
});
|
||||
}).toThrow();
|
||||
|
||||
expect(storage.endpoint['package-policy-edit']).toEqual({
|
||||
view: 'package-policy-edit',
|
||||
package: 'endpoint',
|
||||
component: LazyCustomView,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { UIExtensionRegistrationCallback, UIExtensionsStorage } from '../types';
|
||||
|
||||
/** Factory that returns a callback that can be used to register UI extensions */
|
||||
export const createExtensionRegistrationCallback = (
|
||||
storage: UIExtensionsStorage
|
||||
): UIExtensionRegistrationCallback => {
|
||||
return (extensionPoint) => {
|
||||
const { package: packageName, view } = extensionPoint;
|
||||
|
||||
if (!storage[packageName]) {
|
||||
storage[packageName] = {};
|
||||
}
|
||||
|
||||
if (storage[packageName]?.[view]) {
|
||||
throw new Error(`Extension point has already been registered: [${packageName}][${view}]`);
|
||||
}
|
||||
|
||||
storage[packageName][view] = extensionPoint;
|
||||
};
|
||||
};
|
|
@ -119,3 +119,5 @@ export {
|
|||
} from '../../../../common';
|
||||
|
||||
export * from './intra_app_route_state';
|
||||
|
||||
export * from './ui_extensions';
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ComponentType, LazyExoticComponent } from 'react';
|
||||
import { NewPackagePolicy, PackagePolicy } from './index';
|
||||
|
||||
/** Register a Fleet UI extension */
|
||||
export type UIExtensionRegistrationCallback = (extensionPoint: UIExtensionPoint) => void;
|
||||
|
||||
/** Internal storage for registered UI Extension Points */
|
||||
export interface UIExtensionsStorage {
|
||||
[key: string]: Partial<Record<UIExtensionPoint['view'], UIExtensionPoint>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* UI Component Extension is used on the pages displaying the ability to edit an
|
||||
* Integration Policy
|
||||
*/
|
||||
export type PackagePolicyEditExtensionComponent = ComponentType<
|
||||
PackagePolicyEditExtensionComponentProps
|
||||
>;
|
||||
|
||||
export interface PackagePolicyEditExtensionComponentProps {
|
||||
/** The current integration policy being edited */
|
||||
policy: PackagePolicy;
|
||||
/** The new (updated) integration policy that will be saved */
|
||||
newPolicy: NewPackagePolicy;
|
||||
/**
|
||||
* A callback that should be executed anytime a change to the Integration Policy needs to
|
||||
* be reported back to the Fleet Policy Edit page
|
||||
*/
|
||||
onChange: (opts: {
|
||||
/** is current form state is valid */
|
||||
isValid: boolean;
|
||||
/** The updated Integration Policy to be merged back and included in the API call */
|
||||
updatedPolicy: NewPackagePolicy;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
/** Extension point registration contract for Integration Policy Edit views */
|
||||
export interface PackagePolicyEditExtension {
|
||||
package: string;
|
||||
view: 'package-policy-edit';
|
||||
component: LazyExoticComponent<PackagePolicyEditExtensionComponent>;
|
||||
}
|
||||
|
||||
/**
|
||||
* UI Component Extension is used on the pages displaying the ability to Create an
|
||||
* Integration Policy
|
||||
*/
|
||||
export type PackagePolicyCreateExtensionComponent = ComponentType<
|
||||
PackagePolicyCreateExtensionComponentProps
|
||||
>;
|
||||
|
||||
export interface PackagePolicyCreateExtensionComponentProps {
|
||||
/** The integration policy being created */
|
||||
newPolicy: NewPackagePolicy;
|
||||
/**
|
||||
* A callback that should be executed anytime a change to the Integration Policy needs to
|
||||
* be reported back to the Fleet Policy Edit page
|
||||
*/
|
||||
onChange: (opts: {
|
||||
/** is current form state is valid */
|
||||
isValid: boolean;
|
||||
/** The updated Integration Policy to be merged back and included in the API call */
|
||||
updatedPolicy: NewPackagePolicy;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
/** Extension point registration contract for Integration Policy Create views */
|
||||
export interface PackagePolicyCreateExtension {
|
||||
package: string;
|
||||
view: 'package-policy-create';
|
||||
component: LazyExoticComponent<PackagePolicyCreateExtensionComponent>;
|
||||
}
|
||||
|
||||
/**
|
||||
* UI Component Extension is used to display a Custom tab (and view) under a given Integration
|
||||
*/
|
||||
export type PackageCustomExtensionComponent = ComponentType;
|
||||
|
||||
/** Extension point registration contract for Integration details Custom view */
|
||||
export interface PackageCustomExtension {
|
||||
package: string;
|
||||
view: 'package-detail-custom';
|
||||
component: LazyExoticComponent<PackageCustomExtensionComponent>;
|
||||
}
|
||||
|
||||
/** Fleet UI Extension Point */
|
||||
export type UIExtensionPoint =
|
||||
| PackagePolicyEditExtension
|
||||
| PackageCustomExtension
|
||||
| PackagePolicyCreateExtension;
|
|
@ -12,13 +12,8 @@ export const plugin = (initializerContext: PluginInitializerContext) => {
|
|||
return new IngestManagerPlugin(initializerContext);
|
||||
};
|
||||
|
||||
export {
|
||||
CustomConfigurePackagePolicyContent,
|
||||
CustomConfigurePackagePolicyProps,
|
||||
registerPackagePolicyComponent,
|
||||
} from './applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy';
|
||||
|
||||
export type { NewPackagePolicy } from './applications/fleet/types';
|
||||
export * from './applications/fleet/types/intra_app_route_state';
|
||||
export * from './applications/fleet/types/ui_extensions';
|
||||
|
||||
export { pagePathGetters } from './applications/fleet/constants';
|
||||
|
|
|
@ -30,7 +30,8 @@ import {
|
|||
TutorialDirectoryHeaderLink,
|
||||
TutorialModuleNotice,
|
||||
} from './applications/fleet/components/home_integration';
|
||||
import { registerPackagePolicyComponent } from './applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy';
|
||||
import { createExtensionRegistrationCallback } from './applications/fleet/services/ui_extensions';
|
||||
import { UIExtensionRegistrationCallback, UIExtensionsStorage } from './applications/fleet/types';
|
||||
|
||||
export { IngestManagerConfigType } from '../common/types';
|
||||
|
||||
|
@ -43,7 +44,7 @@ export interface IngestManagerSetup {}
|
|||
* Describes public IngestManager plugin contract returned at the `start` stage.
|
||||
*/
|
||||
export interface IngestManagerStart {
|
||||
registerPackagePolicyComponent: typeof registerPackagePolicyComponent;
|
||||
registerExtension: UIExtensionRegistrationCallback;
|
||||
isInitialized: () => Promise<true>;
|
||||
}
|
||||
|
||||
|
@ -62,6 +63,7 @@ export class IngestManagerPlugin
|
|||
Plugin<IngestManagerSetup, IngestManagerStart, IngestManagerSetupDeps, IngestManagerStartDeps> {
|
||||
private config: IngestManagerConfigType;
|
||||
private kibanaVersion: string;
|
||||
private extensions: UIExtensionsStorage = {};
|
||||
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {
|
||||
this.config = this.initializerContext.config.get<IngestManagerConfigType>();
|
||||
|
@ -71,6 +73,7 @@ export class IngestManagerPlugin
|
|||
public setup(core: CoreSetup, deps: IngestManagerSetupDeps) {
|
||||
const config = this.config;
|
||||
const kibanaVersion = this.kibanaVersion;
|
||||
const extensions = this.extensions;
|
||||
|
||||
// Set up http client
|
||||
setHttpClient(core.http);
|
||||
|
@ -92,7 +95,15 @@ export class IngestManagerPlugin
|
|||
IngestManagerStart
|
||||
];
|
||||
const { renderApp, teardownIngestManager } = await import('./applications/fleet/');
|
||||
const unmount = renderApp(coreStart, params, deps, startDeps, config, kibanaVersion);
|
||||
const unmount = renderApp(
|
||||
coreStart,
|
||||
params,
|
||||
deps,
|
||||
startDeps,
|
||||
config,
|
||||
kibanaVersion,
|
||||
extensions
|
||||
);
|
||||
|
||||
return () => {
|
||||
unmount();
|
||||
|
@ -153,7 +164,8 @@ export class IngestManagerPlugin
|
|||
|
||||
return successPromise;
|
||||
},
|
||||
registerPackagePolicyComponent,
|
||||
|
||||
registerExtension: createExtensionRegistrationCallback(this.extensions),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { IngestManagerStart, registerPackagePolicyComponent } from '../../../../../fleet/public';
|
||||
import { IngestManagerStart } from '../../../../../fleet/public';
|
||||
import {
|
||||
dataPluginMock,
|
||||
Start as DataPublicStartMock,
|
||||
|
@ -58,7 +58,7 @@ export const depsStartMock: () => DepsStartMock = () => {
|
|||
data: dataMock,
|
||||
ingestManager: {
|
||||
isInitialized: () => Promise.resolve(true),
|
||||
registerPackagePolicyComponent,
|
||||
registerExtension: jest.fn(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { PackagePolicyCreateExtensionComponentProps } from '../../../../../../../fleet/public';
|
||||
|
||||
/**
|
||||
* Exports Endpoint-specific package policy instructions
|
||||
* for use in the Ingest app create / edit package policy
|
||||
*/
|
||||
export const EndpointPolicyCreateExtension = memo<PackagePolicyCreateExtensionComponentProps>(
|
||||
() => {
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut data-test-subj="endpointPackagePolicy_create" iconType="iInCircle">
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.endpointConfiguration"
|
||||
defaultMessage="We'll save your integration with our recommended defaults. You can change this later by editing the Endpoint Security integration within your agent policy."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiCallOut>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
EndpointPolicyCreateExtension.displayName = 'EndpointPolicyCreateExtension';
|
|
@ -20,9 +20,8 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
CustomConfigurePackagePolicyContent,
|
||||
CustomConfigurePackagePolicyProps,
|
||||
pagePathGetters,
|
||||
PackagePolicyEditExtensionComponentProps,
|
||||
} from '../../../../../../../fleet/public';
|
||||
import { getPolicyDetailPath, getTrustedAppsListPath } from '../../../../common/routing';
|
||||
import { MANAGEMENT_APP_ID } from '../../../../common/constants';
|
||||
|
@ -37,42 +36,21 @@ import { useNavigateToAppEventHandler } from '../../../../../common/hooks/endpoi
|
|||
* Exports Endpoint-specific package policy instructions
|
||||
* for use in the Ingest app create / edit package policy
|
||||
*/
|
||||
export const ConfigureEndpointPackagePolicy = memo<CustomConfigurePackagePolicyContent>(
|
||||
({
|
||||
from,
|
||||
packagePolicyId,
|
||||
packagePolicy: { policy_id: agentPolicyId },
|
||||
}: CustomConfigurePackagePolicyProps) => {
|
||||
export const EndpointPolicyEditExtension = memo<PackagePolicyEditExtensionComponentProps>(
|
||||
({ policy }) => {
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut
|
||||
data-test-subj={`endpointPackagePolicy_${from === 'edit' ? 'edit' : 'create'}`}
|
||||
iconType="iInCircle"
|
||||
>
|
||||
<EuiCallOut data-test-subj="endpointPackagePolicy_edit" iconType="iInCircle">
|
||||
<EuiText size="s">
|
||||
{from === 'edit' ? (
|
||||
<>
|
||||
<EditFlowMessage
|
||||
agentPolicyId={agentPolicyId}
|
||||
integrationPolicyId={packagePolicyId!}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.endpointConfiguration"
|
||||
defaultMessage="We'll save your integration with our recommended defaults. You can change this later by editing the Endpoint Security integration within your agent policy."
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
<EditFlowMessage agentPolicyId={policy.policy_id} integrationPolicyId={policy.id} />
|
||||
</EuiText>
|
||||
</EuiCallOut>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
ConfigureEndpointPackagePolicy.displayName = 'ConfigureEndpointPackagePolicy';
|
||||
EndpointPolicyEditExtension.displayName = 'EndpointPolicyEditExtension';
|
||||
|
||||
const EditFlowMessage = memo<{
|
||||
agentPolicyId: string;
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
import { PackagePolicyCreateExtensionComponent } from '../../../../../../../fleet/public';
|
||||
|
||||
export const LazyEndpointPolicyCreateExtension = lazy<PackagePolicyCreateExtensionComponent>(
|
||||
async () => {
|
||||
const { EndpointPolicyCreateExtension } = await import('./endpoint_policy_create_extension');
|
||||
return {
|
||||
default: EndpointPolicyCreateExtension,
|
||||
};
|
||||
}
|
||||
);
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
import { PackagePolicyEditExtensionComponent } from '../../../../../../../fleet/public';
|
||||
|
||||
export const LazyEndpointPolicyEditExtension = lazy<PackagePolicyEditExtensionComponent>(
|
||||
async () => {
|
||||
const { EndpointPolicyEditExtension } = await import('./endpoint_policy_edit_extension');
|
||||
return {
|
||||
// FIXME: remove casting once old UI component registration is removed
|
||||
default: (EndpointPolicyEditExtension as unknown) as PackagePolicyEditExtensionComponent,
|
||||
};
|
||||
}
|
||||
);
|
|
@ -7,7 +7,6 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { pluck } from 'rxjs/operators';
|
||||
|
||||
import {
|
||||
PluginSetup,
|
||||
PluginStart,
|
||||
|
@ -44,8 +43,6 @@ import {
|
|||
DEFAULT_INDEX_KEY,
|
||||
} from '../common/constants';
|
||||
|
||||
import { ConfigureEndpointPackagePolicy } from './management/pages/policy/view/ingest_manager_integration/configure_package_policy';
|
||||
|
||||
import { SecurityPageName } from './app/types';
|
||||
import { manageOldSiemRoutes } from './helpers';
|
||||
import {
|
||||
|
@ -63,6 +60,8 @@ import {
|
|||
} from '../common/search_strategy/index_fields';
|
||||
import { SecurityAppStore } from './common/store/store';
|
||||
import { getCaseConnectorUI } from './common/lib/connectors';
|
||||
import { LazyEndpointPolicyEditExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension';
|
||||
import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension';
|
||||
|
||||
export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, StartPlugins> {
|
||||
private kibanaVersion: string;
|
||||
|
@ -332,10 +331,19 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
public start(core: CoreStart, plugins: StartPlugins) {
|
||||
KibanaServices.init({ ...core, ...plugins, kibanaVersion: this.kibanaVersion });
|
||||
if (plugins.ingestManager) {
|
||||
plugins.ingestManager.registerPackagePolicyComponent(
|
||||
'endpoint',
|
||||
ConfigureEndpointPackagePolicy
|
||||
);
|
||||
const { registerExtension } = plugins.ingestManager;
|
||||
|
||||
registerExtension({
|
||||
package: 'endpoint',
|
||||
view: 'package-policy-edit',
|
||||
component: LazyEndpointPolicyEditExtension,
|
||||
});
|
||||
|
||||
registerExtension({
|
||||
package: 'endpoint',
|
||||
view: 'package-policy-create',
|
||||
component: LazyEndpointPolicyCreateExtension,
|
||||
});
|
||||
}
|
||||
|
||||
return {};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue