[ENDPOINT][INGEST]Task/endpoint ingest update (#67234)

Custom endpoint Datasource configuration in ingest

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: Jen Huang <its.jenetic@gmail.com>
This commit is contained in:
Candace Park 2020-06-03 17:30:40 -04:00 committed by GitHub
parent 77abbf257d
commit dd86ccf747
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 186 additions and 44 deletions

View file

@ -5,7 +5,7 @@
*/
export { useCapabilities } from './use_capabilities';
export { useCore, CoreContext } from './use_core';
export { useCore } from './use_core';
export { useConfig, ConfigContext } from './use_config';
export { useSetupDeps, useStartDeps, DepsContext } from './use_deps';
export { useBreadcrumbs } from './use_breadcrumbs';

View file

@ -4,15 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useContext } from 'react';
import { CoreStart } from 'src/core/public';
import { CoreStart } from 'kibana/public';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
export const CoreContext = React.createContext<CoreStart | null>(null);
export function useCore() {
const core = useContext(CoreContext);
if (core === null) {
throw new Error('CoreContext not initialized');
export function useCore(): CoreStart {
const { services } = useKibana();
if (services === null) {
throw new Error('KibanaContextProvider not initialized');
}
return core;
return services;
}

View file

@ -22,11 +22,12 @@ import { PAGE_ROUTING_PATHS } from './constants';
import { DefaultLayout, WithoutHeaderLayout } from './layouts';
import { Loading, Error } from './components';
import { IngestManagerOverview, EPMApp, AgentConfigApp, FleetApp, DataStreamApp } from './sections';
import { CoreContext, DepsContext, ConfigContext, setHttpClient, useConfig } from './hooks';
import { DepsContext, ConfigContext, setHttpClient, useConfig } from './hooks';
import { PackageInstallProvider } from './sections/epm/hooks';
import { useCore, sendSetup, sendGetPermissionsCheck } from './hooks';
import { FleetStatusProvider } from './hooks/use_fleet_status';
import './index.scss';
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
export interface ProtectedRouteProps extends RouteProps {
isAllowed?: boolean;
@ -229,7 +230,7 @@ const IngestManagerApp = ({
const isDarkMode = useObservable<boolean>(coreStart.uiSettings.get$('theme:darkMode'));
return (
<coreStart.i18n.Context>
<CoreContext.Provider value={coreStart}>
<KibanaContextProvider services={{ ...coreStart }}>
<DepsContext.Provider value={{ setup: setupDeps, start: startDeps }}>
<ConfigContext.Provider value={config}>
<EuiThemeProvider darkMode={isDarkMode}>
@ -237,7 +238,7 @@ const IngestManagerApp = ({
</EuiThemeProvider>
</ConfigContext.Provider>
</DepsContext.Provider>
</CoreContext.Provider>
</KibanaContextProvider>
</coreStart.i18n.Context>
);
};

View file

@ -0,0 +1,61 @@
/*
* 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 { NewDatasource } from '../../../../types';
import { CreateDatasourceFrom } from '../types';
export interface CustomConfigureDatasourceProps {
packageName: string;
from: CreateDatasourceFrom;
datasource: NewDatasource | (NewDatasource & { id: string });
}
/**
* Custom content type that external plugins can provide to Ingest's
* Datasource configuration.
*/
export type CustomConfigureDatasourceContent = React.FC<CustomConfigureDatasourceProps>;
type AllowedDatasourceKey = 'endpoint';
const ConfigureDatasourceMapping: {
[key: string]: CustomConfigureDatasourceContent;
} = {};
/**
* Plugins can call this function from the start lifecycle to
* register a custom component in the Ingest Datasource configuration.
*/
export function registerDatasource(
key: AllowedDatasourceKey,
value: CustomConfigureDatasourceContent
) {
ConfigureDatasourceMapping[key] = value;
}
const EmptyConfigureDatasource: CustomConfigureDatasourceContent = () => (
<EuiEmptyPrompt
iconType="checkInCircleFilled"
iconColor="secondary"
body={
<EuiText>
<p>
<FormattedMessage
id="xpack.ingestManager.createDatasource.stepConfigure.noConfigOptionsMessage"
defaultMessage="Nothing to configure"
/>
</p>
</EuiText>
}
/>
);
export const CustomConfigureDatasource = (props: CustomConfigureDatasourceProps) => {
const ConfigureDatasourceContent =
ConfigureDatasourceMapping[props.packageName] || EmptyConfigureDatasource;
return <ConfigureDatasourceContent {...props} />;
};

View file

@ -6,3 +6,4 @@
export { CreateDatasourcePageLayout } from './layout';
export { DatasourceInputPanel } from './datasource_input_panel';
export { DatasourceInputVarField } from './datasource_input_var_field';
export { CustomConfigureDatasource } from './custom_configure_datasource';

View file

@ -5,28 +5,29 @@
*/
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiPanel,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiEmptyPrompt,
EuiText,
EuiCallOut,
} from '@elastic/eui';
import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { PackageInfo, NewDatasource, DatasourceInput } from '../../../types';
import { Loading } from '../../../components';
import { DatasourceValidationResults, validationHasErrors } from './services';
import { DatasourceInputPanel } from './components';
import { DatasourceInputPanel, CustomConfigureDatasource } from './components';
import { CreateDatasourceFrom } from './types';
export const StepConfigureDatasource: React.FunctionComponent<{
from?: CreateDatasourceFrom;
packageInfo: PackageInfo;
datasource: NewDatasource;
datasource: NewDatasource | (NewDatasource & { id: string });
updateDatasource: (fields: Partial<NewDatasource>) => void;
validationResults: DatasourceValidationResults;
submitAttempted: boolean;
}> = ({ packageInfo, datasource, updateDatasource, validationResults, submitAttempted }) => {
}> = ({
from = 'config',
packageInfo,
datasource,
updateDatasource,
validationResults,
submitAttempted,
}) => {
const hasErrors = validationResults ? validationHasErrors(validationResults) : false;
// Configure inputs (and their streams)
@ -68,19 +69,10 @@ export const StepConfigureDatasource: React.FunctionComponent<{
</EuiFlexGroup>
) : (
<EuiPanel>
<EuiEmptyPrompt
iconType="checkInCircleFilled"
iconColor="secondary"
body={
<EuiText>
<p>
<FormattedMessage
id="xpack.ingestManager.createDatasource.stepConfigure.noConfigOptionsMessage"
defaultMessage="Nothing to configure"
/>
</p>
</EuiText>
}
<CustomConfigureDatasource
packageName={packageInfo.name}
datasource={datasource}
from={from}
/>
</EuiPanel>
);

View file

@ -69,7 +69,8 @@ export const EditDatasourcePage: React.FunctionComponent = () => {
const [loadingError, setLoadingError] = useState<Error>();
const [agentConfig, setAgentConfig] = useState<AgentConfig>();
const [packageInfo, setPackageInfo] = useState<PackageInfo>();
const [datasource, setDatasource] = useState<NewDatasource>({
const [datasource, setDatasource] = useState<NewDatasource & { id: string }>({
id: '',
name: '',
description: '',
config_id: '',
@ -93,7 +94,6 @@ export const EditDatasourcePage: React.FunctionComponent = () => {
}
if (datasourceData?.item) {
const {
id,
revision,
inputs,
created_by,
@ -299,6 +299,7 @@ export const EditDatasourcePage: React.FunctionComponent = () => {
),
children: (
<StepConfigureDatasource
from={'edit'}
packageInfo={packageInfo}
datasource={datasource}
updateDatasource={updateDatasource}

View file

@ -11,3 +11,11 @@ export { IngestManagerStart } from './plugin';
export const plugin = (initializerContext: PluginInitializerContext) => {
return new IngestManagerPlugin(initializerContext);
};
export {
CustomConfigureDatasourceContent,
CustomConfigureDatasourceProps,
registerDatasource,
} from './applications/ingest_manager/sections/agent_config/create_datasource_page/components/custom_configure_datasource';
export { NewDatasource } from './applications/ingest_manager/types';

View file

@ -18,6 +18,7 @@ import { PLUGIN_ID } from '../common/constants';
import { IngestManagerConfigType } from '../common/types';
import { setupRouteService, appRoutesService } from '../common';
import { registerDatasource } from './applications/ingest_manager/sections/agent_config/create_datasource_page/components/custom_configure_datasource';
export { IngestManagerConfigType } from '../common/types';
@ -26,6 +27,7 @@ export type IngestManagerSetup = void;
* Describes public IngestManager plugin contract returned at the `start` stage.
*/
export interface IngestManagerStart {
registerDatasource: typeof registerDatasource;
success: boolean;
error?: {
message: string;
@ -80,12 +82,16 @@ export class IngestManagerPlugin
const permissionsResponse = await core.http.get(appRoutesService.getCheckPermissionsPath());
if (permissionsResponse.success) {
const { isInitialized: success } = await core.http.post(setupRouteService.getSetupPath());
return { success };
return { success, registerDatasource };
} else {
throw new Error(permissionsResponse.error);
}
} catch (error) {
return { success: false, error: { message: error.body?.message || 'Unknown error' } };
return {
success: false,
error: { message: error.body?.message || 'Unknown error' },
registerDatasource,
};
}
}

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { IngestManagerStart } from '../../../../../ingest_manager/public';
import { IngestManagerStart, registerDatasource } from '../../../../../ingest_manager/public';
import {
dataPluginMock,
Start as DataPublicStartMock,
@ -56,6 +56,6 @@ export const depsStartMock: () => DepsStartMock = () => {
return {
data: dataMock,
ingestManager: { success: true },
ingestManager: { success: true, registerDatasource },
};
};

View file

@ -0,0 +1,72 @@
/*
* 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 { FormattedMessage } from '@kbn/i18n/react';
import { EuiEmptyPrompt, EuiText } from '@elastic/eui';
import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public';
import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app';
import {
CustomConfigureDatasourceContent,
CustomConfigureDatasourceProps,
NewDatasource,
} from '../../../../../../../ingest_manager/public';
import { getManagementUrl } from '../../../..';
type DatasourceWithId = NewDatasource & { id: string };
/**
* Exports Endpoint-specific datasource configuration instructions
* for use in the Ingest app create / edit datasource config
*/
export const ConfigureEndpointDatasource = memo<CustomConfigureDatasourceContent>(
({
from,
datasource,
}: {
from: string;
datasource: CustomConfigureDatasourceProps['datasource'];
}) => {
const { services } = useKibana();
let policyUrl = '';
if (from === 'edit') {
policyUrl = getManagementUrl({
name: 'policyDetails',
policyId: (datasource as DatasourceWithId).id,
});
}
return (
<EuiEmptyPrompt
body={
<EuiText>
<p>
{from === 'edit' ? (
<LinkToApp
appId="siem"
appPath={policyUrl}
href={`${services.application.getUrlForApp('siem')}${policyUrl}`}
>
<FormattedMessage
id="xpack.siem.endpoint.ingestManager.editDatasource.stepConfigure"
defaultMessage="View and configure Security Policy"
/>
</LinkToApp>
) : (
<FormattedMessage
id="xpack.siem.endpoint.ingestManager.createDatasource.stepConfigure"
defaultMessage="The recommended Security Policy has been associated with this data source. The Security Policy can be edited in the Security application once your data source has been saved."
/>
)}
</p>
</EuiText>
}
/>
);
}
);
ConfigureEndpointDatasource.displayName = 'ConfigureEndpointDatasource';

View file

@ -20,6 +20,7 @@ import { KibanaServices } from './common/lib/kibana/services';
import { serviceNowActionType, jiraActionType } from './common/lib/connectors';
import { PluginSetup, PluginStart, SetupPlugins, StartPlugins, StartServices } from './types';
import { APP_ID, APP_NAME, APP_ICON, APP_PATH } from '../common/constants';
import { ConfigureEndpointDatasource } from './management/pages/policy/view/ingest_manager_integration/configure_datasource';
export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, StartPlugins> {
private kibanaVersion: string;
@ -131,6 +132,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
public start(core: CoreStart, plugins: StartPlugins) {
KibanaServices.init({ ...core, ...plugins, kibanaVersion: this.kibanaVersion });
plugins.ingestManager.registerDatasource('endpoint', ConfigureEndpointDatasource);
return {};
}