Skip to content

Commit

Permalink
[ENDPOINT][INGEST]Task/endpoint ingest update (#67234) (#68181)
Browse files Browse the repository at this point in the history
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>

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: Jen Huang <its.jenetic@gmail.com>
  • Loading branch information
3 people committed Jun 4, 2020
1 parent 9d59b46 commit 34d7010
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -229,15 +230,15 @@ 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}>
<IngestManagerRoutes basepath={basepath} />
</EuiThemeProvider>
</ConfigContext.Provider>
</DepsContext.Provider>
</CoreContext.Provider>
</KibanaContextProvider>
</coreStart.i18n.Context>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: '',
Expand All @@ -93,7 +94,6 @@ export const EditDatasourcePage: React.FunctionComponent = () => {
}
if (datasourceData?.item) {
const {
id,
revision,
inputs,
created_by,
Expand Down Expand Up @@ -299,6 +299,7 @@ export const EditDatasourcePage: React.FunctionComponent = () => {
),
children: (
<StepConfigureDatasource
from={'edit'}
packageInfo={packageInfo}
datasource={datasource}
updateDatasource={updateDatasource}
Expand Down
8 changes: 8 additions & 0 deletions x-pack/plugins/ingest_manager/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
10 changes: 8 additions & 2 deletions x-pack/plugins/ingest_manager/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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;
Expand Down Expand Up @@ -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,
};
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -56,6 +56,6 @@ export const depsStartMock: () => DepsStartMock = () => {

return {
data: dataMock,
ingestManager: { success: true },
ingestManager: { success: true, registerDatasource },
};
};
Original file line number Diff line number Diff line change
@@ -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';
2 changes: 2 additions & 0 deletions x-pack/plugins/siem/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {};
}
Expand Down

0 comments on commit 34d7010

Please sign in to comment.