[Ingest] Add Global settings flyout (#64276)
This commit is contained in:
parent
8c05a724e7
commit
6c98b2368b
33 changed files with 899 additions and 54 deletions
|
@ -12,3 +12,4 @@ export * from './datasource';
|
|||
export * from './epm';
|
||||
export * from './output';
|
||||
export * from './enrollment_api_key';
|
||||
export * from './settings';
|
||||
|
|
|
@ -48,6 +48,19 @@ export const AGENT_CONFIG_API_ROUTES = {
|
|||
FULL_INFO_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/full`,
|
||||
};
|
||||
|
||||
// Output API routes
|
||||
export const OUTPUT_API_ROUTES = {
|
||||
LIST_PATTERN: `${API_ROOT}/outputs`,
|
||||
INFO_PATTERN: `${API_ROOT}/outputs/{outputId}`,
|
||||
UPDATE_PATTERN: `${API_ROOT}/outputs/{outputId}`,
|
||||
};
|
||||
|
||||
// Settings API routes
|
||||
export const SETTINGS_API_ROUTES = {
|
||||
INFO_PATTERN: `${API_ROOT}/settings`,
|
||||
UPDATE_PATTERN: `${API_ROOT}/settings`,
|
||||
};
|
||||
|
||||
// Agent API routes
|
||||
export const AGENT_API_ROUTES = {
|
||||
LIST_PATTERN: `${FLEET_API_ROOT}/agents`,
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const GLOBAL_SETTINGS_SAVED_OBJECT_TYPE = 'ingest_manager_settings';
|
|
@ -13,6 +13,8 @@ import {
|
|||
AGENT_API_ROUTES,
|
||||
ENROLLMENT_API_KEY_ROUTES,
|
||||
SETUP_API_ROUTE,
|
||||
OUTPUT_API_ROUTES,
|
||||
SETTINGS_API_ROUTES,
|
||||
} from '../constants';
|
||||
|
||||
export const epmRouteService = {
|
||||
|
@ -112,6 +114,18 @@ export const agentRouteService = {
|
|||
getStatusPath: () => AGENT_API_ROUTES.STATUS_PATTERN,
|
||||
};
|
||||
|
||||
export const outputRoutesService = {
|
||||
getInfoPath: (outputId: string) => OUTPUT_API_ROUTES.INFO_PATTERN.replace('{outputId}', outputId),
|
||||
getUpdatePath: (outputId: string) =>
|
||||
OUTPUT_API_ROUTES.UPDATE_PATTERN.replace('{outputId}', outputId),
|
||||
getListPath: () => OUTPUT_API_ROUTES.LIST_PATTERN,
|
||||
};
|
||||
|
||||
export const settingsRoutesService = {
|
||||
getInfoPath: () => SETTINGS_API_ROUTES.INFO_PATTERN,
|
||||
getUpdatePath: () => SETTINGS_API_ROUTES.UPDATE_PATTERN,
|
||||
};
|
||||
|
||||
export const enrollmentAPIKeyRouteService = {
|
||||
getListPath: () => ENROLLMENT_API_KEY_ROUTES.LIST_PATTERN,
|
||||
getCreatePath: () => ENROLLMENT_API_KEY_ROUTES.CREATE_PATTERN,
|
||||
|
|
|
@ -11,3 +11,4 @@ export * from './data_stream';
|
|||
export * from './output';
|
||||
export * from './epm';
|
||||
export * from './enrollment_api_key';
|
||||
export * from './settings';
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { SavedObjectAttributes } from 'src/core/public';
|
||||
|
||||
interface BaseSettings {
|
||||
agent_auto_upgrade?: boolean;
|
||||
package_auto_upgrade?: boolean;
|
||||
kibana_url?: string;
|
||||
kibana_ca_sha256?: string;
|
||||
}
|
||||
|
||||
export interface Settings extends BaseSettings {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface SettingsSOAttributes extends BaseSettings, SavedObjectAttributes {}
|
|
@ -12,3 +12,5 @@ export * from './fleet_setup';
|
|||
export * from './epm';
|
||||
export * from './enrollment_api_key';
|
||||
export * from './install_script';
|
||||
export * from './output';
|
||||
export * from './settings';
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 { Output } from '../models';
|
||||
|
||||
export interface GetOneOutputResponse {
|
||||
item: Output;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface GetOneOutputRequest {
|
||||
params: {
|
||||
outputId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PutOutputRequest {
|
||||
params: {
|
||||
outputId: string;
|
||||
};
|
||||
body: {
|
||||
hosts?: string[];
|
||||
ca_sha256?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PutOutputResponse {
|
||||
item: Output;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface GetOutputsResponse {
|
||||
items: Output[];
|
||||
total: number;
|
||||
page: number;
|
||||
perPage: number;
|
||||
success: boolean;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { Settings } from '../models';
|
||||
|
||||
export interface GetSettingsResponse {
|
||||
item: Settings;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface PutSettingsRequest {
|
||||
body: Partial<Omit<Settings, 'id'>>;
|
||||
}
|
||||
|
||||
export interface PutSettingsResponse {
|
||||
item: Settings;
|
||||
success: boolean;
|
||||
}
|
|
@ -7,3 +7,4 @@ export { Loading } from './loading';
|
|||
export { Error } from './error';
|
||||
export { Header, HeaderProps } from './header';
|
||||
export { AlphaMessaging } from './alpha_messaging';
|
||||
export * from './settings_flyout';
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* 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, { useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutHeader,
|
||||
EuiTitle,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiSpacer,
|
||||
EuiButton,
|
||||
EuiFlyoutFooter,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiFieldText,
|
||||
EuiRadioGroup,
|
||||
EuiComboBox,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { useInput, useComboInput, useCore, useGetSettings, sendPutSettings } from '../hooks';
|
||||
import { useGetOutputs, sendPutOutput } from '../hooks/use_request/outputs';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function useSettingsForm(outputId: string | undefined) {
|
||||
const { notifications } = useCore();
|
||||
const kibanaUrlInput = useInput();
|
||||
const elasticsearchUrlInput = useComboInput([]);
|
||||
|
||||
return {
|
||||
onSubmit: async () => {
|
||||
try {
|
||||
if (!outputId) {
|
||||
throw new Error('Unable to load outputs');
|
||||
}
|
||||
await sendPutOutput(outputId, {
|
||||
hosts: elasticsearchUrlInput.value,
|
||||
});
|
||||
await sendPutSettings({
|
||||
kibana_url: kibanaUrlInput.value,
|
||||
});
|
||||
} catch (error) {
|
||||
notifications.toasts.addError(error, {
|
||||
title: 'Error',
|
||||
});
|
||||
}
|
||||
notifications.toasts.addSuccess(
|
||||
i18n.translate('xpack.ingestManager.settings.success.message', {
|
||||
defaultMessage: 'Settings saved',
|
||||
})
|
||||
);
|
||||
},
|
||||
inputs: {
|
||||
kibanaUrl: kibanaUrlInput,
|
||||
elasticsearchUrl: elasticsearchUrlInput,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const SettingFlyout: React.FunctionComponent<Props> = ({ onClose }) => {
|
||||
const core = useCore();
|
||||
const settingsRequest = useGetSettings();
|
||||
const settings = settingsRequest?.data?.item;
|
||||
const outputsRequest = useGetOutputs();
|
||||
const output = outputsRequest.data?.items?.[0];
|
||||
const { inputs, onSubmit } = useSettingsForm(output?.id);
|
||||
|
||||
useEffect(() => {
|
||||
if (output) {
|
||||
inputs.elasticsearchUrl.setValue(output.hosts || []);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [output]);
|
||||
|
||||
useEffect(() => {
|
||||
if (settings) {
|
||||
inputs.kibanaUrl.setValue(
|
||||
settings.kibana_url || `${window.location.origin}${core.http.basePath.get()}`
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [settings]);
|
||||
|
||||
const body = (
|
||||
<EuiForm>
|
||||
<EuiRadioGroup
|
||||
options={[
|
||||
{
|
||||
id: 'enabled',
|
||||
label: i18n.translate('xpack.ingestManager.settings.autoUpgradeEnabledLabel', {
|
||||
defaultMessage:
|
||||
'Automatically update agent binaries to use the latest minor version.',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'disabled',
|
||||
disabled: true,
|
||||
label: i18n.translate('xpack.ingestManager.settings.autoUpgradeDisabledLabel', {
|
||||
defaultMessage: 'Manually manage agent binary versions. Requires Gold license.',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
idSelected={'enabled'}
|
||||
onChange={id => {}}
|
||||
legend={{
|
||||
children: (
|
||||
<EuiTitle size="xs">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.settings.autoUpgradeFieldLabel"
|
||||
defaultMessage="Elastic Agent binary version"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiRadioGroup
|
||||
options={[
|
||||
{
|
||||
id: 'enabled',
|
||||
label: i18n.translate(
|
||||
'xpack.ingestManager.settings.integrationUpgradeEnabledFieldLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'Automatically update Integrations to the latest version to receive the latest assets. Agent configurations may need to be updated in order to use new features.',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'disabled',
|
||||
disabled: true,
|
||||
label: i18n.translate(
|
||||
'xpack.ingestManager.settings.integrationUpgradeDisabledFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Manually manage integration versions yourself.',
|
||||
}
|
||||
),
|
||||
},
|
||||
]}
|
||||
idSelected={'enabled'}
|
||||
onChange={id => {}}
|
||||
legend={{
|
||||
children: (
|
||||
<EuiTitle size="xs">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.settings.integrationUpgradeFieldLabel"
|
||||
defaultMessage="Elastic integration version"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.settings.globalOutputTitle"
|
||||
defaultMessage="Global output"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText color="subdued" size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.settings.globalOutputDescription"
|
||||
defaultMessage="The global output is applied to all agent configurations and specifies where data is sent."
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.ingestManager.settings.kibanaUrlLabel', {
|
||||
defaultMessage: 'Kibana URL',
|
||||
})}
|
||||
>
|
||||
<EuiFieldText required={true} {...inputs.kibanaUrl.props} name="kibanaUrl" />
|
||||
</EuiFormRow>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.ingestManager.settings.elasticsearchUrlLabel', {
|
||||
defaultMessage: 'Elasticsearch URL',
|
||||
})}
|
||||
>
|
||||
<EuiComboBox noSuggestions {...inputs.elasticsearchUrl.props} />
|
||||
</EuiFormRow>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={onClose} size="l" maxWidth={640}>
|
||||
<EuiFlyoutHeader hasBorder aria-labelledby="IngestManagerSettingsFlyoutTitle">
|
||||
<EuiTitle size="m">
|
||||
<h2 id="IngestManagerSettingsFlyoutTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.settings.flyoutTitle"
|
||||
defaultMessage="Ingest Management settings"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>{body}</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty iconType="cross" onClick={onClose} flush="left">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.settings.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={onSubmit} iconType="save">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.settings.saveButtonLabel"
|
||||
defaultMessage="Save settings"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
|
@ -20,5 +20,27 @@ export function useInput(defaultValue = '') {
|
|||
clear: () => {
|
||||
setValue('');
|
||||
},
|
||||
setValue,
|
||||
};
|
||||
}
|
||||
|
||||
export function useComboInput(defaultValue = []) {
|
||||
const [value, setValue] = React.useState<string[]>(defaultValue);
|
||||
|
||||
return {
|
||||
props: {
|
||||
selectedOptions: value.map((val: string) => ({ label: val })),
|
||||
onCreateOption: (newVal: any) => {
|
||||
setValue([...value, newVal]);
|
||||
},
|
||||
onChange: (newVals: any[]) => {
|
||||
setValue(newVals.map(val => val.label));
|
||||
},
|
||||
},
|
||||
value,
|
||||
clear: () => {
|
||||
setValue([]);
|
||||
},
|
||||
setValue,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -10,3 +10,5 @@ export * from './data_stream';
|
|||
export * from './agents';
|
||||
export * from './enrollment_api_keys';
|
||||
export * from './epm';
|
||||
export * from './outputs';
|
||||
export * from './settings';
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { sendRequest, useRequest } from './use_request';
|
||||
import { outputRoutesService } from '../../services';
|
||||
import { PutOutputRequest, GetOutputsResponse } from '../../types';
|
||||
|
||||
export function useGetOutputs() {
|
||||
return useRequest<GetOutputsResponse>({
|
||||
method: 'get',
|
||||
path: outputRoutesService.getListPath(),
|
||||
});
|
||||
}
|
||||
|
||||
export function sendPutOutput(outputId: string, body: PutOutputRequest['body']) {
|
||||
return sendRequest({
|
||||
method: 'put',
|
||||
path: outputRoutesService.getUpdatePath(outputId),
|
||||
body,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { sendRequest, useRequest } from './use_request';
|
||||
import { settingsRoutesService } from '../../services';
|
||||
import { PutSettingsResponse, PutSettingsRequest, GetSettingsResponse } from '../../types';
|
||||
|
||||
export function useGetSettings() {
|
||||
return useRequest<GetSettingsResponse>({
|
||||
method: 'get',
|
||||
path: settingsRoutesService.getInfoPath(),
|
||||
});
|
||||
}
|
||||
|
||||
export function sendPutSettings(body: PutSettingsRequest['body']) {
|
||||
return sendRequest<PutSettingsResponse>({
|
||||
method: 'put',
|
||||
path: settingsRoutesService.getUpdatePath(),
|
||||
body,
|
||||
});
|
||||
}
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiTabs, EuiTab, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui';
|
||||
import { EuiTabs, EuiTab, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiButtonEmpty } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { Section } from '../sections';
|
||||
import { AlphaMessaging } from '../components';
|
||||
import { AlphaMessaging, SettingFlyout } from '../components';
|
||||
import { useLink, useConfig } from '../hooks';
|
||||
import { EPM_PATH, FLEET_PATH, AGENT_CONFIG_PATH, DATA_STREAM_PATH } from '../constants';
|
||||
|
||||
|
@ -35,59 +35,79 @@ const Nav = styled.nav`
|
|||
|
||||
export const DefaultLayout: React.FunctionComponent<Props> = ({ section, children }) => {
|
||||
const { epm, fleet } = useConfig();
|
||||
|
||||
const [isSettingsFlyoutOpen, setIsSettingsFlyoutOpen] = React.useState(false);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Nav>
|
||||
<EuiFlexGroup gutterSize="l" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="savedObjectsApp" size="l" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTabs display="condensed">
|
||||
<EuiTab isSelected={section === 'overview'} href={useLink()}>
|
||||
<>
|
||||
{isSettingsFlyoutOpen && (
|
||||
<SettingFlyout
|
||||
onClose={() => {
|
||||
setIsSettingsFlyoutOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Container>
|
||||
<Nav>
|
||||
<EuiFlexGroup gutterSize="l" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="savedObjectsApp" size="l" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTabs display="condensed">
|
||||
<EuiTab isSelected={section === 'overview'} href={useLink()}>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.appNavigation.overviewLinkText"
|
||||
defaultMessage="Overview"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
isSelected={section === 'epm'}
|
||||
href={useLink(EPM_PATH)}
|
||||
disabled={!epm?.enabled}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.appNavigation.epmLinkText"
|
||||
defaultMessage="Integrations"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab isSelected={section === 'agent_config'} href={useLink(AGENT_CONFIG_PATH)}>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.appNavigation.configurationsLinkText"
|
||||
defaultMessage="Configurations"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
isSelected={section === 'fleet'}
|
||||
href={useLink(FLEET_PATH)}
|
||||
disabled={!fleet?.enabled}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.appNavigation.fleetLinkText"
|
||||
defaultMessage="Fleet"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab isSelected={section === 'data_stream'} href={useLink(DATA_STREAM_PATH)}>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.appNavigation.dataStreamsLinkText"
|
||||
defaultMessage="Data streams"
|
||||
/>
|
||||
</EuiTab>
|
||||
</EuiTabs>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty iconType="gear" onClick={() => setIsSettingsFlyoutOpen(true)}>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.appNavigation.overviewLinkText"
|
||||
defaultMessage="Overview"
|
||||
id="xpack.ingestManager.appNavigation.settingsButton"
|
||||
defaultMessage="General settings"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
isSelected={section === 'epm'}
|
||||
href={useLink(EPM_PATH)}
|
||||
disabled={!epm?.enabled}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.appNavigation.epmLinkText"
|
||||
defaultMessage="Integrations"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab isSelected={section === 'agent_config'} href={useLink(AGENT_CONFIG_PATH)}>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.appNavigation.configurationsLinkText"
|
||||
defaultMessage="Configurations"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
isSelected={section === 'fleet'}
|
||||
href={useLink(FLEET_PATH)}
|
||||
disabled={!fleet?.enabled}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.appNavigation.fleetLinkText"
|
||||
defaultMessage="Fleet"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab isSelected={section === 'data_stream'} href={useLink(DATA_STREAM_PATH)}>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.appNavigation.dataStreamsLinkText"
|
||||
defaultMessage="Data streams"
|
||||
/>
|
||||
</EuiTab>
|
||||
</EuiTabs>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Nav>
|
||||
{children}
|
||||
<AlphaMessaging />
|
||||
</Container>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Nav>
|
||||
{children}
|
||||
<AlphaMessaging />
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -15,6 +15,8 @@ export {
|
|||
enrollmentAPIKeyRouteService,
|
||||
epmRouteService,
|
||||
setupRouteService,
|
||||
outputRoutesService,
|
||||
settingsRoutesService,
|
||||
packageToConfigDatasourceInputs,
|
||||
storedDatasourceToAgentDatasource,
|
||||
AgentStatusKueryHelper,
|
||||
|
|
|
@ -17,6 +17,7 @@ export {
|
|||
DatasourceInput,
|
||||
DatasourceInputStream,
|
||||
DatasourceConfigRecordEntry,
|
||||
Output,
|
||||
DataStream,
|
||||
// API schemas - Agent Config
|
||||
GetAgentConfigsResponse,
|
||||
|
@ -48,6 +49,14 @@ export {
|
|||
GetEnrollmentAPIKeysResponse,
|
||||
GetEnrollmentAPIKeysRequest,
|
||||
GetOneEnrollmentAPIKeyResponse,
|
||||
// API schemas - Outputs
|
||||
GetOutputsResponse,
|
||||
PutOutputRequest,
|
||||
PutOutputResponse,
|
||||
// API schemas - Settings
|
||||
GetSettingsResponse,
|
||||
PutSettingsRequest,
|
||||
PutSettingsResponse,
|
||||
// EPM types
|
||||
AssetReference,
|
||||
AssetsGroupedByServiceByType,
|
||||
|
|
|
@ -19,7 +19,9 @@ export {
|
|||
FLEET_SETUP_API_ROUTES,
|
||||
ENROLLMENT_API_KEY_ROUTES,
|
||||
INSTALL_SCRIPT_API_ROUTES,
|
||||
OUTPUT_API_ROUTES,
|
||||
SETUP_API_ROUTE,
|
||||
SETTINGS_API_ROUTES,
|
||||
// Saved object types
|
||||
AGENT_SAVED_OBJECT_TYPE,
|
||||
AGENT_EVENT_SAVED_OBJECT_TYPE,
|
||||
|
@ -30,6 +32,7 @@ export {
|
|||
PACKAGES_SAVED_OBJECT_TYPE,
|
||||
INDEX_PATTERN_SAVED_OBJECT_TYPE,
|
||||
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
|
||||
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE as GLOBAL_SETTINGS_SAVED_OBJET_TYPE,
|
||||
// Defaults
|
||||
DEFAULT_AGENT_CONFIG,
|
||||
DEFAULT_OUTPUT,
|
||||
|
|
|
@ -39,6 +39,8 @@ import {
|
|||
registerAgentRoutes,
|
||||
registerEnrollmentApiKeyRoutes,
|
||||
registerInstallScriptRoutes,
|
||||
registerOutputRoutes,
|
||||
registerSettingsRoutes,
|
||||
} from './routes';
|
||||
|
||||
import { IngestManagerConfigType } from '../common';
|
||||
|
@ -150,6 +152,8 @@ export class IngestManagerPlugin
|
|||
// Register routes
|
||||
registerAgentConfigRoutes(router);
|
||||
registerDatasourceRoutes(router);
|
||||
registerOutputRoutes(router);
|
||||
registerSettingsRoutes(router);
|
||||
registerDataStreamRoutes(router);
|
||||
|
||||
// Conditional routes
|
||||
|
|
|
@ -11,3 +11,5 @@ export { registerRoutes as registerSetupRoutes } from './setup';
|
|||
export { registerRoutes as registerAgentRoutes } from './agent';
|
||||
export { registerRoutes as registerEnrollmentApiKeyRoutes } from './enrollment_api_key';
|
||||
export { registerRoutes as registerInstallScriptRoutes } from './install_script';
|
||||
export { registerRoutes as registerOutputRoutes } from './output';
|
||||
export { registerRoutes as registerSettingsRoutes } from './settings';
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 { RequestHandler } from 'src/core/server';
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import { GetOneOutputRequestSchema, PutOutputRequestSchema } from '../../types';
|
||||
import { GetOneOutputResponse, GetOutputsResponse } from '../../../common';
|
||||
import { outputService } from '../../services/output';
|
||||
|
||||
export const getOutputsHandler: RequestHandler = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
try {
|
||||
const outputs = await outputService.list(soClient);
|
||||
|
||||
const body: GetOutputsResponse = {
|
||||
items: outputs.items,
|
||||
page: outputs.page,
|
||||
perPage: outputs.perPage,
|
||||
total: outputs.total,
|
||||
success: true,
|
||||
};
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (e) {
|
||||
return response.customError({
|
||||
statusCode: 500,
|
||||
body: { message: e.message },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const getOneOuputHandler: RequestHandler<TypeOf<
|
||||
typeof GetOneOutputRequestSchema.params
|
||||
>> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
try {
|
||||
const output = await outputService.get(soClient, request.params.outputId);
|
||||
|
||||
const body: GetOneOutputResponse = {
|
||||
item: output,
|
||||
success: true,
|
||||
};
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (e) {
|
||||
if (e.isBoom && e.output.statusCode === 404) {
|
||||
return response.notFound({
|
||||
body: { message: `Output ${request.params.outputId} not found` },
|
||||
});
|
||||
}
|
||||
|
||||
return response.customError({
|
||||
statusCode: 500,
|
||||
body: { message: e.message },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const putOuputHandler: RequestHandler<
|
||||
TypeOf<typeof PutOutputRequestSchema.params>,
|
||||
undefined,
|
||||
TypeOf<typeof PutOutputRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
try {
|
||||
await outputService.update(soClient, request.params.outputId, request.body);
|
||||
const output = await outputService.get(soClient, request.params.outputId);
|
||||
|
||||
const body: GetOneOutputResponse = {
|
||||
item: output,
|
||||
success: true,
|
||||
};
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (e) {
|
||||
if (e.isBoom && e.output.statusCode === 404) {
|
||||
return response.notFound({
|
||||
body: { message: `Output ${request.params.outputId} not found` },
|
||||
});
|
||||
}
|
||||
|
||||
return response.customError({
|
||||
statusCode: 500,
|
||||
body: { message: e.message },
|
||||
});
|
||||
}
|
||||
};
|
41
x-pack/plugins/ingest_manager/server/routes/output/index.ts
Normal file
41
x-pack/plugins/ingest_manager/server/routes/output/index.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 { IRouter } from 'src/core/server';
|
||||
import { PLUGIN_ID, OUTPUT_API_ROUTES } from '../../constants';
|
||||
import { getOneOuputHandler, getOutputsHandler, putOuputHandler } from './handler';
|
||||
import {
|
||||
GetOneOutputRequestSchema,
|
||||
GetOutputsRequestSchema,
|
||||
PutOutputRequestSchema,
|
||||
} from '../../types';
|
||||
|
||||
export const registerRoutes = (router: IRouter) => {
|
||||
router.get(
|
||||
{
|
||||
path: OUTPUT_API_ROUTES.LIST_PATTERN,
|
||||
validate: GetOutputsRequestSchema,
|
||||
options: { tags: [`access:${PLUGIN_ID}-read`] },
|
||||
},
|
||||
getOutputsHandler
|
||||
);
|
||||
router.get(
|
||||
{
|
||||
path: OUTPUT_API_ROUTES.INFO_PATTERN,
|
||||
validate: GetOneOutputRequestSchema,
|
||||
options: { tags: [`access:${PLUGIN_ID}-read`] },
|
||||
},
|
||||
getOneOuputHandler
|
||||
);
|
||||
router.put(
|
||||
{
|
||||
path: OUTPUT_API_ROUTES.UPDATE_PATTERN,
|
||||
validate: PutOutputRequestSchema,
|
||||
options: { tags: [`access:${PLUGIN_ID}-read`] },
|
||||
},
|
||||
putOuputHandler
|
||||
);
|
||||
};
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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 { IRouter, RequestHandler } from 'src/core/server';
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import { PLUGIN_ID, SETTINGS_API_ROUTES } from '../../constants';
|
||||
import { PutSettingsRequestSchema, GetSettingsRequestSchema } from '../../types';
|
||||
|
||||
import { settingsService } from '../../services';
|
||||
|
||||
export const getSettingsHandler: RequestHandler = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
|
||||
try {
|
||||
const settings = await settingsService.getSettings(soClient);
|
||||
const body = {
|
||||
success: true,
|
||||
item: settings,
|
||||
};
|
||||
return response.ok({ body });
|
||||
} catch (e) {
|
||||
if (e.isBoom && e.output.statusCode === 404) {
|
||||
return response.notFound({
|
||||
body: { message: `Setings not found` },
|
||||
});
|
||||
}
|
||||
|
||||
return response.customError({
|
||||
statusCode: 500,
|
||||
body: { message: e.message },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const putSettingsHandler: RequestHandler<
|
||||
undefined,
|
||||
undefined,
|
||||
TypeOf<typeof PutSettingsRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
try {
|
||||
const settings = await settingsService.saveSettings(soClient, request.body);
|
||||
const body = {
|
||||
success: true,
|
||||
item: settings,
|
||||
};
|
||||
return response.ok({ body });
|
||||
} catch (e) {
|
||||
if (e.isBoom && e.output.statusCode === 404) {
|
||||
return response.notFound({
|
||||
body: { message: `Setings not found` },
|
||||
});
|
||||
}
|
||||
|
||||
return response.customError({
|
||||
statusCode: 500,
|
||||
body: { message: e.message },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const registerRoutes = (router: IRouter) => {
|
||||
router.get(
|
||||
{
|
||||
path: SETTINGS_API_ROUTES.INFO_PATTERN,
|
||||
validate: GetSettingsRequestSchema,
|
||||
options: { tags: [`access:${PLUGIN_ID}-read`] },
|
||||
},
|
||||
getSettingsHandler
|
||||
);
|
||||
router.put(
|
||||
{
|
||||
path: SETTINGS_API_ROUTES.UPDATE_PATTERN,
|
||||
validate: PutSettingsRequestSchema,
|
||||
options: { tags: [`access:${PLUGIN_ID}-all`] },
|
||||
},
|
||||
putSettingsHandler
|
||||
);
|
||||
};
|
|
@ -15,6 +15,7 @@ import {
|
|||
AGENT_EVENT_SAVED_OBJECT_TYPE,
|
||||
AGENT_ACTION_SAVED_OBJECT_TYPE,
|
||||
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
|
||||
GLOBAL_SETTINGS_SAVED_OBJET_TYPE,
|
||||
} from './constants';
|
||||
|
||||
/*
|
||||
|
@ -22,7 +23,24 @@ import {
|
|||
*
|
||||
* Please update typings in `/common/types` if mappings are updated.
|
||||
*/
|
||||
|
||||
const savedObjectTypes: { [key: string]: SavedObjectsType } = {
|
||||
[GLOBAL_SETTINGS_SAVED_OBJET_TYPE]: {
|
||||
name: GLOBAL_SETTINGS_SAVED_OBJET_TYPE,
|
||||
hidden: false,
|
||||
namespaceType: 'agnostic',
|
||||
management: {
|
||||
importableAndExportable: false,
|
||||
},
|
||||
mappings: {
|
||||
properties: {
|
||||
agent_auto_upgrade: { type: 'keyword' },
|
||||
package_auto_upgrade: { type: 'keyword' },
|
||||
kibana_url: { type: 'keyword' },
|
||||
kibana_ca_sha256: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
},
|
||||
[AGENT_SAVED_OBJECT_TYPE]: {
|
||||
name: AGENT_SAVED_OBJECT_TYPE,
|
||||
hidden: false,
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'kibana/server';
|
||||
import { AgentStatus } from '../../common/types/models';
|
||||
|
||||
import * as settingsService from './settings';
|
||||
export { ESIndexPatternSavedObjectService } from './es_index_pattern';
|
||||
|
||||
/**
|
||||
|
@ -35,6 +36,7 @@ export interface AgentService {
|
|||
export { datasourceService } from './datasource';
|
||||
export { agentConfigService } from './agent_config';
|
||||
export { outputService } from './output';
|
||||
export { settingsService };
|
||||
|
||||
// Plugin services
|
||||
export { appContextService } from './app_context';
|
||||
|
|
|
@ -95,6 +95,34 @@ class OutputService {
|
|||
...outputSO.attributes,
|
||||
};
|
||||
}
|
||||
|
||||
public async update(soClient: SavedObjectsClientContract, id: string, data: Partial<Output>) {
|
||||
const outputSO = await soClient.update<Output>(SAVED_OBJECT_TYPE, id, data);
|
||||
|
||||
if (outputSO.error) {
|
||||
throw new Error(outputSO.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async list(soClient: SavedObjectsClientContract) {
|
||||
const outputs = await soClient.find<Output>({
|
||||
type: SAVED_OBJECT_TYPE,
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
});
|
||||
|
||||
return {
|
||||
items: outputs.saved_objects.map<Output>(outputSO => {
|
||||
return {
|
||||
id: outputSO.id,
|
||||
...outputSO.attributes,
|
||||
};
|
||||
}),
|
||||
total: outputs.total,
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const outputService = new OutputService();
|
||||
|
|
57
x-pack/plugins/ingest_manager/server/services/settings.ts
Normal file
57
x-pack/plugins/ingest_manager/server/services/settings.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 Boom from 'boom';
|
||||
import { SavedObjectsClientContract } from 'kibana/server';
|
||||
import { GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, SettingsSOAttributes, Settings } from '../../common';
|
||||
|
||||
export async function getSettings(soClient: SavedObjectsClientContract): Promise<Settings> {
|
||||
const res = await soClient.find<SettingsSOAttributes>({
|
||||
type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
});
|
||||
|
||||
if (res.total === 0) {
|
||||
throw Boom.notFound('Global settings not found');
|
||||
}
|
||||
const settingsSo = res.saved_objects[0];
|
||||
return {
|
||||
id: settingsSo.id,
|
||||
...settingsSo.attributes,
|
||||
};
|
||||
}
|
||||
|
||||
export async function saveSettings(
|
||||
soClient: SavedObjectsClientContract,
|
||||
newData: Partial<Omit<Settings, 'id'>>
|
||||
): Promise<Settings> {
|
||||
try {
|
||||
const settings = await getSettings(soClient);
|
||||
|
||||
const res = await soClient.update<SettingsSOAttributes>(
|
||||
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
settings.id,
|
||||
newData
|
||||
);
|
||||
|
||||
return {
|
||||
id: settings.id,
|
||||
...res.attributes,
|
||||
};
|
||||
} catch (e) {
|
||||
if (e.isBoom && e.output.statusCode === 404) {
|
||||
const res = await soClient.create<SettingsSOAttributes>(
|
||||
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
newData
|
||||
);
|
||||
|
||||
return {
|
||||
id: res.id,
|
||||
...res.attributes,
|
||||
};
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
|
@ -21,6 +21,8 @@ import {
|
|||
import { getPackageInfo } from './epm/packages';
|
||||
import { datasourceService } from './datasource';
|
||||
import { generateEnrollmentAPIKey } from './api_keys';
|
||||
import { settingsService } from '.';
|
||||
import { appContextService } from './app_context';
|
||||
|
||||
const FLEET_ENROLL_USERNAME = 'fleet_enroll';
|
||||
const FLEET_ENROLL_ROLE = 'fleet_enroll';
|
||||
|
@ -34,6 +36,17 @@ export async function setupIngestManager(
|
|||
ensureInstalledDefaultPackages(soClient, callCluster),
|
||||
outputService.ensureDefaultOutput(soClient),
|
||||
agentConfigService.ensureDefaultAgentConfig(soClient),
|
||||
settingsService.getSettings(soClient).catch((e: any) => {
|
||||
if (e.isBoom && e.output.statusCode === 404) {
|
||||
return settingsService.saveSettings(soClient, {
|
||||
agent_auto_upgrade: true,
|
||||
package_auto_upgrade: true,
|
||||
kibana_url: appContextService.getConfig()?.fleet?.kibana?.host,
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.reject(e);
|
||||
}),
|
||||
]);
|
||||
|
||||
// ensure default packages are added to the default conifg
|
||||
|
|
|
@ -50,6 +50,8 @@ export {
|
|||
DefaultPackages,
|
||||
TemplateRef,
|
||||
IndexTemplateMappings,
|
||||
Settings,
|
||||
SettingsSOAttributes,
|
||||
} from '../../common';
|
||||
|
||||
export type CallESAsCurrentUser = ScopedClusterClient['callAsCurrentUser'];
|
||||
|
|
|
@ -10,3 +10,5 @@ export * from './datasource';
|
|||
export * from './epm';
|
||||
export * from './enrollment_api_key';
|
||||
export * from './install_script';
|
||||
export * from './output';
|
||||
export * from './settings';
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
|
||||
export const GetOneOutputRequestSchema = {
|
||||
params: schema.object({
|
||||
outputId: schema.string(),
|
||||
}),
|
||||
};
|
||||
|
||||
export const GetOutputsRequestSchema = {};
|
||||
|
||||
export const PutOutputRequestSchema = {
|
||||
params: schema.object({
|
||||
outputId: schema.string(),
|
||||
}),
|
||||
body: schema.object({
|
||||
hosts: schema.maybe(schema.arrayOf(schema.string())),
|
||||
ca_sha256: schema.maybe(schema.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 { schema } from '@kbn/config-schema';
|
||||
|
||||
export const GetSettingsRequestSchema = {};
|
||||
|
||||
export const PutSettingsRequestSchema = {
|
||||
body: schema.object({
|
||||
agent_auto_upgrade: schema.maybe(schema.boolean()),
|
||||
package_auto_upgrade: schema.maybe(schema.boolean()),
|
||||
kibana_url: schema.maybe(schema.string()),
|
||||
kibana_ca_sha256: schema.maybe(schema.string()),
|
||||
}),
|
||||
};
|
Loading…
Reference in a new issue