mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[APM] Fleet: APM Integration settings (fleet editor) redesign (#106535)
* initial commig * adding forms * moving settings_form * moving settings_form * renaming * fixing import * adding error message per settings * fixing onchange bug * adding isValid to onchange func * fixing TS issue * fixing default value * validating form * refactoring to validate form * renaming to vars * refactoring * adding apm integration settings * fixing some stff * refactoring name * adding translations * refactoring * fixing test * adding unit test * flats settings * refactoring cloud policy constant * removing fleet exported function * fixing TS issue * fixing bug * fixing ts * addressing PR comments * addressing PR comments * addressing PR comments
This commit is contained in:
parent
b9acd3ce74
commit
f50843dc52
20 changed files with 1358 additions and 9 deletions
8
x-pack/plugins/apm/common/fleet.ts
Normal file
8
x-pack/plugins/apm/common/fleet.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const POLICY_ELASTIC_AGENT_ON_CLOUD = 'policy-elastic-agent-on-cloud';
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { APMPolicyForm } from '.';
|
||||
import {
|
||||
PackagePolicyVars,
|
||||
NewPackagePolicy,
|
||||
PackagePolicyCreateExtensionComponentProps,
|
||||
} from './typings';
|
||||
|
||||
interface Props {
|
||||
newPolicy: NewPackagePolicy;
|
||||
onChange: PackagePolicyCreateExtensionComponentProps['onChange'];
|
||||
}
|
||||
|
||||
export function CreateAPMPolicyForm({ newPolicy, onChange }: Props) {
|
||||
const [firstInput, ...restInputs] = newPolicy?.inputs;
|
||||
const vars = firstInput?.vars;
|
||||
|
||||
function handleChange(newVars: PackagePolicyVars, isValid: boolean) {
|
||||
onChange({
|
||||
isValid,
|
||||
updatedPolicy: {
|
||||
...newPolicy,
|
||||
inputs: [{ ...firstInput, vars: newVars }, ...restInputs],
|
||||
},
|
||||
});
|
||||
}
|
||||
return (
|
||||
<APMPolicyForm vars={vars} onChange={handleChange} isCloudPolicy={false} />
|
||||
);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { APMPolicyForm } from '.';
|
||||
import {
|
||||
NewPackagePolicy,
|
||||
PackagePolicy,
|
||||
PackagePolicyEditExtensionComponentProps,
|
||||
PackagePolicyVars,
|
||||
} from './typings';
|
||||
import { POLICY_ELASTIC_AGENT_ON_CLOUD } from '../../../../common/fleet';
|
||||
|
||||
interface Props {
|
||||
policy: PackagePolicy;
|
||||
newPolicy: NewPackagePolicy;
|
||||
onChange: PackagePolicyEditExtensionComponentProps['onChange'];
|
||||
}
|
||||
|
||||
export function EditAPMPolicyForm({ newPolicy, onChange }: Props) {
|
||||
const [firstInput, ...restInputs] = newPolicy?.inputs;
|
||||
const vars = firstInput?.vars;
|
||||
|
||||
function handleChange(newVars: PackagePolicyVars, isValid: boolean) {
|
||||
onChange({
|
||||
isValid,
|
||||
updatedPolicy: {
|
||||
inputs: [{ ...firstInput, vars: newVars }, ...restInputs],
|
||||
},
|
||||
});
|
||||
}
|
||||
return (
|
||||
<APMPolicyForm
|
||||
vars={vars}
|
||||
onChange={handleChange}
|
||||
isCloudPolicy={newPolicy.policy_id === POLICY_ELASTIC_AGENT_ON_CLOUD}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { OnFormChangeFn, PackagePolicyVars } from './typings';
|
||||
import { APMSettingsForm } from './settings/apm_settings';
|
||||
import { RUMSettingsForm } from './settings/rum_settings';
|
||||
import { TLSSettingsForm } from './settings/tls_settings';
|
||||
|
||||
interface Props {
|
||||
onChange: OnFormChangeFn;
|
||||
vars?: PackagePolicyVars;
|
||||
isCloudPolicy: boolean;
|
||||
}
|
||||
|
||||
export function APMPolicyForm({ vars = {}, isCloudPolicy, onChange }: Props) {
|
||||
return (
|
||||
<>
|
||||
<APMSettingsForm
|
||||
vars={vars}
|
||||
onChange={onChange}
|
||||
isCloudPolicy={isCloudPolicy}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<RUMSettingsForm vars={vars} onChange={onChange} />
|
||||
<EuiSpacer />
|
||||
<TLSSettingsForm vars={vars} onChange={onChange} />
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useMemo } from 'react';
|
||||
import { getDurationRt } from '../../../../../common/agent_configuration/runtime_types/duration_rt';
|
||||
import { getIntegerRt } from '../../../../../common/agent_configuration/runtime_types/integer_rt';
|
||||
import { OnFormChangeFn, PackagePolicyVars } from '../typings';
|
||||
import { SettingsForm } from './settings_form';
|
||||
import { SettingDefinition } from './typings';
|
||||
import {
|
||||
isSettingsFormValid,
|
||||
mergeNewVars,
|
||||
OPTIONAL_LABEL,
|
||||
REQUIRED_LABEL,
|
||||
} from './utils';
|
||||
|
||||
interface Props {
|
||||
vars: PackagePolicyVars;
|
||||
onChange: OnFormChangeFn;
|
||||
isCloudPolicy: boolean;
|
||||
}
|
||||
|
||||
function getApmSettings(isCloudPolicy: boolean): SettingDefinition[] {
|
||||
return [
|
||||
{
|
||||
type: 'text',
|
||||
key: 'host',
|
||||
labelAppend: REQUIRED_LABEL,
|
||||
readOnly: isCloudPolicy,
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.hostLabel',
|
||||
{ defaultMessage: 'Host' }
|
||||
),
|
||||
rowTitle: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.hostTitle',
|
||||
{ defaultMessage: 'Server configuration' }
|
||||
),
|
||||
rowDescription: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.hostDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Choose a name and description to help identify how this integration will be used.',
|
||||
}
|
||||
),
|
||||
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'url',
|
||||
labelAppend: REQUIRED_LABEL,
|
||||
readOnly: isCloudPolicy,
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.urlLabel',
|
||||
{
|
||||
defaultMessage: 'URL',
|
||||
}
|
||||
),
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'secret_token',
|
||||
readOnly: isCloudPolicy,
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.secretTokenLabel',
|
||||
{ defaultMessage: 'Secret token' }
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
key: 'api_key_enabled',
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
placeholder: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.apiKeyAuthenticationPlaceholder',
|
||||
{ defaultMessage: 'API key for agent authentication' }
|
||||
),
|
||||
helpText: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.apiKeyAuthenticationHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Enable API Key auth between APM Server and APM Agents.',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'advanced_settings',
|
||||
settings: [
|
||||
{
|
||||
key: 'max_header_bytes',
|
||||
type: 'integer',
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.maxHeaderBytesLabel',
|
||||
{ defaultMessage: "Maximum size of a request's header (bytes)" }
|
||||
),
|
||||
rowTitle: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.maxHeaderBytesTitle',
|
||||
{ defaultMessage: 'Limits' }
|
||||
),
|
||||
rowDescription: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.maxHeaderBytesDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Set limits on request headers sizes and timing configurations.',
|
||||
}
|
||||
),
|
||||
validation: getIntegerRt({ min: 1 }),
|
||||
},
|
||||
{
|
||||
key: 'idle_timeout',
|
||||
type: 'text',
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.idleTimeoutLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'Idle time before underlying connection is closed',
|
||||
}
|
||||
),
|
||||
validation: getDurationRt({ min: '1ms' }),
|
||||
},
|
||||
{
|
||||
key: 'read_timeout',
|
||||
type: 'text',
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.readTimeoutLabel',
|
||||
{ defaultMessage: 'Maximum duration for reading an entire request' }
|
||||
),
|
||||
validation: getDurationRt({ min: '1ms' }),
|
||||
},
|
||||
{
|
||||
key: 'shutdown_timeout',
|
||||
type: 'text',
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.shutdownTimeoutLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'Maximum duration before releasing resources when shutting down',
|
||||
}
|
||||
),
|
||||
validation: getDurationRt({ min: '1ms' }),
|
||||
},
|
||||
{
|
||||
key: 'write_timeout',
|
||||
type: 'text',
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.writeTimeoutLabel',
|
||||
{ defaultMessage: 'Maximum duration for writing a response' }
|
||||
),
|
||||
validation: getDurationRt({ min: '1ms' }),
|
||||
},
|
||||
{
|
||||
key: 'max_event_bytes',
|
||||
type: 'integer',
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.maxEventBytesLabel',
|
||||
{ defaultMessage: 'Maximum size per event (bytes)' }
|
||||
),
|
||||
validation: getIntegerRt({ min: 1 }),
|
||||
},
|
||||
{
|
||||
key: 'max_connections',
|
||||
type: 'integer',
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.maxConnectionsLabel',
|
||||
{ defaultMessage: 'Simultaneously accepted connections' }
|
||||
),
|
||||
validation: getIntegerRt({ min: 1 }),
|
||||
},
|
||||
{
|
||||
key: 'response_headers',
|
||||
type: 'area',
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.responseHeadersLabel',
|
||||
{ defaultMessage: 'Custom HTTP headers added to HTTP responses' }
|
||||
),
|
||||
helpText: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.responseHeadersHelpText',
|
||||
{ defaultMessage: 'Might be used for security policy compliance.' }
|
||||
),
|
||||
rowTitle: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.responseHeadersTitle',
|
||||
{ defaultMessage: 'Custom headers' }
|
||||
),
|
||||
rowDescription: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.responseHeadersDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Set limits on request headers sizes and timing configurations.',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'api_key_limit',
|
||||
type: 'integer',
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.apiKeyLimitLabel',
|
||||
{ defaultMessage: 'Number of keys' }
|
||||
),
|
||||
helpText: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.apiKeyLimitHelpText',
|
||||
{ defaultMessage: 'Might be used for security policy compliance.' }
|
||||
),
|
||||
rowTitle: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.apiKeyLimitTitle',
|
||||
{
|
||||
defaultMessage:
|
||||
'Maximum number of API keys of Agent authentication',
|
||||
}
|
||||
),
|
||||
rowDescription: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.apiKeyLimitDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Restrict number of unique API keys per minute, used for auth between aPM Agents and Server.',
|
||||
}
|
||||
),
|
||||
validation: getIntegerRt({ min: 1 }),
|
||||
},
|
||||
{
|
||||
key: 'capture_personal_data',
|
||||
type: 'boolean',
|
||||
rowTitle: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.capturePersonalDataTitle',
|
||||
{ defaultMessage: 'Capture personal data' }
|
||||
),
|
||||
rowDescription: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.capturePersonalDataDescription',
|
||||
{ defaultMessage: 'Capture personal data such as IP or User Agent' }
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'default_service_environment',
|
||||
type: 'text',
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.defaultServiceEnvironmentLabel',
|
||||
{ defaultMessage: 'Default Service Environment' }
|
||||
),
|
||||
rowTitle: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.defaultServiceEnvironmentTitle',
|
||||
{ defaultMessage: 'Service configuration' }
|
||||
),
|
||||
rowDescription: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.defaultServiceEnvironmentDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Default service environment to record in events which have no service environment defined.',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'expvar_enabled',
|
||||
type: 'boolean',
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
rowTitle: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.expvarEnabledTitle',
|
||||
{ defaultMessage: 'Enable APM Server Golang expvar support' }
|
||||
),
|
||||
rowDescription: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.expvarEnabledDescription',
|
||||
{ defaultMessage: 'Exposed under /debug/vars' }
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function APMSettingsForm({ vars, onChange, isCloudPolicy }: Props) {
|
||||
const apmSettings = useMemo(() => {
|
||||
return getApmSettings(isCloudPolicy);
|
||||
}, [isCloudPolicy]);
|
||||
|
||||
return (
|
||||
<SettingsForm
|
||||
title={i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.settings.title',
|
||||
{ defaultMessage: 'General' }
|
||||
)}
|
||||
subtitle={i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.apm.settings.subtitle',
|
||||
{ defaultMessage: 'Settings for the APM integration.' }
|
||||
)}
|
||||
settings={apmSettings}
|
||||
vars={vars}
|
||||
onChange={(key, value) => {
|
||||
const newVars = mergeNewVars(vars, key, value);
|
||||
onChange(newVars, isSettingsFormValid(apmSettings, newVars));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiFieldNumber,
|
||||
EuiFieldText,
|
||||
EuiIcon,
|
||||
EuiSwitch,
|
||||
EuiTextArea,
|
||||
EuiComboBox,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { FormRowOnChange } from './settings_form';
|
||||
import { SettingDefinition } from './typings';
|
||||
|
||||
interface Props {
|
||||
setting: SettingDefinition;
|
||||
value?: any;
|
||||
onChange: FormRowOnChange;
|
||||
}
|
||||
|
||||
const ENABLED_LABEL = i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.enabledLabel',
|
||||
{ defaultMessage: 'Enabled' }
|
||||
);
|
||||
const DISABLED_LABEL = i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.disabledLabel',
|
||||
{ defaultMessage: 'Disabled' }
|
||||
);
|
||||
|
||||
export function FormRowSetting({ setting, value, onChange }: Props) {
|
||||
switch (setting.type) {
|
||||
case 'boolean': {
|
||||
return (
|
||||
<EuiSwitch
|
||||
label={
|
||||
setting.placeholder || (value ? ENABLED_LABEL : DISABLED_LABEL)
|
||||
}
|
||||
checked={value}
|
||||
onChange={(e) => {
|
||||
onChange(setting.key, e.target.checked);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'duration':
|
||||
case 'text': {
|
||||
return (
|
||||
<EuiFieldText
|
||||
readOnly={setting.readOnly}
|
||||
value={value}
|
||||
prepend={setting.readOnly ? <EuiIcon type="lock" /> : undefined}
|
||||
onChange={(e) => {
|
||||
onChange(setting.key, e.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'area': {
|
||||
return (
|
||||
<EuiTextArea
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(setting.key, e.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'bytes':
|
||||
case 'integer': {
|
||||
return (
|
||||
<EuiFieldNumber
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(setting.key, e.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'combo': {
|
||||
const comboOptions = Array.isArray(value)
|
||||
? value.map((label) => ({ label }))
|
||||
: [];
|
||||
return (
|
||||
<EuiComboBox
|
||||
placeholder={i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.selectOrCreateOptions',
|
||||
{ defaultMessage: 'Select or create options' }
|
||||
)}
|
||||
options={comboOptions}
|
||||
selectedOptions={comboOptions}
|
||||
onChange={(option) => {
|
||||
onChange(
|
||||
setting.key,
|
||||
option.map(({ label }) => label)
|
||||
);
|
||||
}}
|
||||
onCreateOption={(newOption) => {
|
||||
onChange(setting.key, [...value, newOption]);
|
||||
}}
|
||||
isClearable={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown type "${setting.type}"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { getIntegerRt } from '../../../../../common/agent_configuration/runtime_types/integer_rt';
|
||||
import { OnFormChangeFn, PackagePolicyVars } from '../typings';
|
||||
import { SettingsForm } from './settings_form';
|
||||
import { SettingDefinition } from './typings';
|
||||
import { isSettingsFormValid, mergeNewVars, OPTIONAL_LABEL } from './utils';
|
||||
|
||||
const ENABLE_RUM_KEY = 'enable_rum';
|
||||
const rumSettings: SettingDefinition[] = [
|
||||
{
|
||||
key: ENABLE_RUM_KEY,
|
||||
type: 'boolean',
|
||||
rowTitle: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.enableRumTitle',
|
||||
{ defaultMessage: 'Enable RUM' }
|
||||
),
|
||||
rowDescription: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.enableRumDescription',
|
||||
{ defaultMessage: 'Enable Real User Monitoring (RUM)' }
|
||||
),
|
||||
settings: [
|
||||
{
|
||||
key: 'rum_allow_headers',
|
||||
type: 'combo',
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumAllowHeaderLabel',
|
||||
{ defaultMessage: 'Allowed origin headers' }
|
||||
),
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
helpText: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumAllowHeaderHelpText',
|
||||
{
|
||||
defaultMessage: 'Allowed Origin headers to be sent by User Agents.',
|
||||
}
|
||||
),
|
||||
rowTitle: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumAllowHeaderTitle',
|
||||
{ defaultMessage: 'Custom headers' }
|
||||
),
|
||||
rowDescription: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumAllowHeaderDescription',
|
||||
{ defaultMessage: 'Configure authentication for the agent' }
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'rum_allow_origins',
|
||||
type: 'combo',
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumAllowOriginsLabel',
|
||||
{ defaultMessage: 'Access-Control-Allow-Headers' }
|
||||
),
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
helpText: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumAllowOriginsHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Supported Access-Control-Allow-Headers in addition to "Content-Type", "Content-Encoding" and "Accept".',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'rum_response_headers',
|
||||
type: 'area',
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumResponseHeadersLabel',
|
||||
{ defaultMessage: 'Custom HTTP response headers' }
|
||||
),
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
helpText: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumResponseHeadersHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Added to RUM responses, e.g. for security policy compliance.',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'advanced_settings',
|
||||
settings: [
|
||||
{
|
||||
key: 'rum_event_rate_limit',
|
||||
type: 'integer',
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumEventRateLimitLabel',
|
||||
{ defaultMessage: 'Rate limit events per IP' }
|
||||
),
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
helpText: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumEventRateLimitHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Maximum number of events allowed per IP per second.',
|
||||
}
|
||||
),
|
||||
rowTitle: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumEventRateLimitTitle',
|
||||
{ defaultMessage: 'Limits' }
|
||||
),
|
||||
rowDescription: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumEventRateLimitDescription',
|
||||
{ defaultMessage: 'Configure authentication for the agent' }
|
||||
),
|
||||
validation: getIntegerRt({ min: 1 }),
|
||||
},
|
||||
{
|
||||
key: 'rum_event_rate_lru_size',
|
||||
type: 'integer',
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumEventRateLRUSizeLabel',
|
||||
{ defaultMessage: 'Rate limit cache size' }
|
||||
),
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
helpText: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumEventRateLRUSizeHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Number of unique IPs to be cached for the rate limiter.',
|
||||
}
|
||||
),
|
||||
validation: getIntegerRt({ min: 1 }),
|
||||
},
|
||||
{
|
||||
key: 'rum_library_pattern',
|
||||
type: 'text',
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumLibraryPatternLabel',
|
||||
{ defaultMessage: 'Library Frame Pattern' }
|
||||
),
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
helpText: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumLibraryPatternHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
"Identify library frames by matching a stacktrace frame's file_name and abs_path against this regexp.",
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'rum_allow_service_names',
|
||||
type: 'combo',
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumAllowServiceNamesLabel',
|
||||
{ defaultMessage: 'Allowed service names' }
|
||||
),
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
rowTitle: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumAllowServiceNamesTitle',
|
||||
{ defaultMessage: 'Allowed service names' }
|
||||
),
|
||||
rowDescription: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.rumAllowServiceNamesDescription',
|
||||
{ defaultMessage: 'Configure authentication for the agent' }
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
interface Props {
|
||||
vars: PackagePolicyVars;
|
||||
onChange: OnFormChangeFn;
|
||||
}
|
||||
|
||||
export function RUMSettingsForm({ vars, onChange }: Props) {
|
||||
return (
|
||||
<SettingsForm
|
||||
title={i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.settings.title',
|
||||
{ defaultMessage: 'Real User Monitoring' }
|
||||
)}
|
||||
subtitle={i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.rum.settings.subtitle',
|
||||
{ defaultMessage: 'Manage the configuration of the RUM JS agent.' }
|
||||
)}
|
||||
settings={rumSettings}
|
||||
vars={vars}
|
||||
onChange={(key, value) => {
|
||||
const newVars = mergeNewVars(vars, key, value);
|
||||
onChange(
|
||||
newVars,
|
||||
// only validates RUM when its flag is enabled
|
||||
!newVars[ENABLE_RUM_KEY].value ||
|
||||
isSettingsFormValid(rumSettings, newVars)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiDescribedFormGroup,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiHorizontalRule,
|
||||
EuiPanel,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState } from 'react';
|
||||
import { PackagePolicyVars } from '../typings';
|
||||
import { FormRowSetting } from './form_row_setting';
|
||||
import { SettingDefinition } from './typings';
|
||||
import { validateSettingValue } from './utils';
|
||||
|
||||
export type FormRowOnChange = (key: string, value: any) => void;
|
||||
|
||||
function FormRow({
|
||||
initialSetting,
|
||||
vars,
|
||||
onChange,
|
||||
}: {
|
||||
initialSetting: SettingDefinition;
|
||||
vars?: PackagePolicyVars;
|
||||
onChange: FormRowOnChange;
|
||||
}) {
|
||||
function getSettingFormRow(setting: SettingDefinition) {
|
||||
if (setting.type === 'advanced_settings') {
|
||||
return (
|
||||
<AdvancedOptions>
|
||||
{setting.settings.map((advancedSetting) =>
|
||||
getSettingFormRow(advancedSetting)
|
||||
)}
|
||||
</AdvancedOptions>
|
||||
);
|
||||
} else {
|
||||
const { key } = setting;
|
||||
const value = vars?.[key]?.value;
|
||||
const { isValid, message } = validateSettingValue(setting, value);
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
<EuiDescribedFormGroup
|
||||
title={<h3>{setting.rowTitle}</h3>}
|
||||
description={setting.rowDescription}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={setting.label}
|
||||
isInvalid={!isValid}
|
||||
error={isValid ? undefined : message}
|
||||
helpText={<EuiText size="xs">{setting.helpText}</EuiText>}
|
||||
labelAppend={
|
||||
<EuiText size="xs" color="subdued">
|
||||
{setting.labelAppend}
|
||||
</EuiText>
|
||||
}
|
||||
>
|
||||
<FormRowSetting
|
||||
setting={setting}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
{setting.settings &&
|
||||
value &&
|
||||
setting.settings.map((childSettings) =>
|
||||
getSettingFormRow(childSettings)
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
return getSettingFormRow(initialSetting);
|
||||
}
|
||||
interface Props {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
settings: SettingDefinition[];
|
||||
vars?: PackagePolicyVars;
|
||||
onChange: FormRowOnChange;
|
||||
}
|
||||
|
||||
export function SettingsForm({
|
||||
title,
|
||||
subtitle,
|
||||
settings,
|
||||
vars,
|
||||
onChange,
|
||||
}: Props) {
|
||||
return (
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<h3>{title}</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s" color="subdued">
|
||||
{subtitle}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin="s" />
|
||||
|
||||
{settings.map((setting) => {
|
||||
return FormRow({
|
||||
initialSetting: setting,
|
||||
vars,
|
||||
onChange,
|
||||
});
|
||||
})}
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
|
||||
function AdvancedOptions({ children }: { children: React.ReactNode }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem />
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
iconType={isOpen ? 'arrowDown' : 'arrowRight'}
|
||||
onClick={() => {
|
||||
setIsOpen((state) => !state);
|
||||
}}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.advancedOptionsLavel',
|
||||
{ defaultMessage: 'Advanced options' }
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{isOpen && (
|
||||
<>
|
||||
<EuiHorizontalRule />
|
||||
{children}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { OnFormChangeFn, PackagePolicyVars } from '../typings';
|
||||
import { SettingsForm } from './settings_form';
|
||||
import { SettingDefinition } from './typings';
|
||||
import {
|
||||
isSettingsFormValid,
|
||||
mergeNewVars,
|
||||
OPTIONAL_LABEL,
|
||||
REQUIRED_LABEL,
|
||||
} from './utils';
|
||||
|
||||
const TLS_ENABLED_KEY = 'tls_enabled';
|
||||
const tlsSettings: SettingDefinition[] = [
|
||||
{
|
||||
key: TLS_ENABLED_KEY,
|
||||
rowTitle: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.tls.tlsEnabledTitle',
|
||||
{ defaultMessage: 'Enable TLS' }
|
||||
),
|
||||
type: 'boolean',
|
||||
settings: [
|
||||
{
|
||||
key: 'tls_certificate',
|
||||
type: 'text',
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.tls.tlsCertificateLabel',
|
||||
{ defaultMessage: 'File path to server certificate' }
|
||||
),
|
||||
rowTitle: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.tls.tlsCertificateTitle',
|
||||
{ defaultMessage: 'TLS certificate' }
|
||||
),
|
||||
labelAppend: REQUIRED_LABEL,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'tls_key',
|
||||
type: 'text',
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.tls.tlsKeyLabel',
|
||||
{ defaultMessage: 'File path to server certificate key' }
|
||||
),
|
||||
labelAppend: REQUIRED_LABEL,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'tls_supported_protocols',
|
||||
type: 'combo',
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.tls.tlsSupportedProtocolsLabel',
|
||||
{ defaultMessage: 'Supported protocol versions' }
|
||||
),
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
},
|
||||
{
|
||||
key: 'tls_cipher_suites',
|
||||
type: 'combo',
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.tls.tlsCipherSuitesLabel',
|
||||
{ defaultMessage: 'Cipher suites for TLS connections' }
|
||||
),
|
||||
helpText: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.tls.tlsCipherSuitesHelpText',
|
||||
{ defaultMessage: 'Not configurable for TLS 1.3.' }
|
||||
),
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
},
|
||||
{
|
||||
key: 'tls_curve_types',
|
||||
type: 'combo',
|
||||
label: i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.tls.tlsCurveTypesLabel',
|
||||
{ defaultMessage: 'Curve types for ECDHE based cipher suites' }
|
||||
),
|
||||
labelAppend: OPTIONAL_LABEL,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
interface Props {
|
||||
vars: PackagePolicyVars;
|
||||
onChange: OnFormChangeFn;
|
||||
}
|
||||
|
||||
export function TLSSettingsForm({ vars, onChange }: Props) {
|
||||
return (
|
||||
<SettingsForm
|
||||
title={i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.tls.settings.title',
|
||||
{ defaultMessage: 'TLS Settings' }
|
||||
)}
|
||||
subtitle={i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.tls.settings.subtitle',
|
||||
{ defaultMessage: 'Settings for TLS certification.' }
|
||||
)}
|
||||
settings={tlsSettings}
|
||||
vars={vars}
|
||||
onChange={(key, value) => {
|
||||
const newVars = mergeNewVars(vars, key, value);
|
||||
onChange(
|
||||
newVars,
|
||||
// only validates TLS when its flag is enabled
|
||||
!newVars[TLS_ENABLED_KEY].value ||
|
||||
isSettingsFormValid(tlsSettings, newVars)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import * as t from 'io-ts';
|
||||
|
||||
export type SettingValidation = t.Type<any, string, unknown>;
|
||||
|
||||
interface AdvancedSettings {
|
||||
type: 'advanced_settings';
|
||||
settings: SettingDefinition[];
|
||||
}
|
||||
|
||||
export interface Setting {
|
||||
type:
|
||||
| 'text'
|
||||
| 'combo'
|
||||
| 'area'
|
||||
| 'boolean'
|
||||
| 'integer'
|
||||
| 'bytes'
|
||||
| 'duration';
|
||||
key: string;
|
||||
rowTitle?: string;
|
||||
rowDescription?: string;
|
||||
label?: string;
|
||||
helpText?: string;
|
||||
placeholder?: string;
|
||||
labelAppend?: string;
|
||||
settings?: SettingDefinition[];
|
||||
validation?: SettingValidation;
|
||||
required?: boolean;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export type SettingDefinition = Setting | AdvancedSettings;
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { getDurationRt } from '../../../../../common/agent_configuration/runtime_types/duration_rt';
|
||||
import { PackagePolicyVars } from '../typings';
|
||||
import { SettingDefinition } from './typings';
|
||||
import {
|
||||
mergeNewVars,
|
||||
isSettingsFormValid,
|
||||
validateSettingValue,
|
||||
} from './utils';
|
||||
|
||||
describe('settings utils', () => {
|
||||
describe('validateSettingValue', () => {
|
||||
it('returns invalid when setting is required and value is empty', () => {
|
||||
const setting: SettingDefinition = {
|
||||
key: 'foo',
|
||||
type: 'text',
|
||||
required: true,
|
||||
};
|
||||
expect(validateSettingValue(setting, undefined)).toEqual({
|
||||
isValid: false,
|
||||
message: 'Required field',
|
||||
});
|
||||
});
|
||||
it('returns valid when setting is NOT required and value is empty', () => {
|
||||
const setting: SettingDefinition = {
|
||||
key: 'foo',
|
||||
type: 'text',
|
||||
};
|
||||
expect(validateSettingValue(setting, undefined)).toEqual({
|
||||
isValid: true,
|
||||
message: '',
|
||||
});
|
||||
});
|
||||
it('returns valid when setting does not have a validation property', () => {
|
||||
const setting: SettingDefinition = {
|
||||
key: 'foo',
|
||||
type: 'text',
|
||||
};
|
||||
expect(validateSettingValue(setting, 'foo')).toEqual({
|
||||
isValid: true,
|
||||
message: '',
|
||||
});
|
||||
});
|
||||
it('returns valid after validating value', () => {
|
||||
const setting: SettingDefinition = {
|
||||
key: 'foo',
|
||||
type: 'text',
|
||||
validation: getDurationRt({ min: '1ms' }),
|
||||
};
|
||||
expect(validateSettingValue(setting, '2ms')).toEqual({
|
||||
isValid: true,
|
||||
message: 'No errors!',
|
||||
});
|
||||
});
|
||||
it('returns invalid after validating value', () => {
|
||||
const setting: SettingDefinition = {
|
||||
key: 'foo',
|
||||
type: 'text',
|
||||
validation: getDurationRt({ min: '1ms' }),
|
||||
};
|
||||
expect(validateSettingValue(setting, 'foo')).toEqual({
|
||||
isValid: false,
|
||||
message: 'Must be greater than 1ms',
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('isSettingsFormValid', () => {
|
||||
const settings: SettingDefinition[] = [
|
||||
{ key: 'foo', type: 'text', required: true },
|
||||
{
|
||||
key: 'bar',
|
||||
type: 'text',
|
||||
settings: [{ type: 'text', key: 'bar_1', required: true }],
|
||||
},
|
||||
{ key: 'baz', type: 'text', validation: getDurationRt({ min: '1ms' }) },
|
||||
{
|
||||
type: 'advanced_settings',
|
||||
settings: [
|
||||
{
|
||||
type: 'text',
|
||||
key: 'advanced_1',
|
||||
required: true,
|
||||
settings: [
|
||||
{
|
||||
type: 'text',
|
||||
key: 'advanced_1_1',
|
||||
validation: getDurationRt({ min: '1ms' }),
|
||||
settings: [
|
||||
{
|
||||
type: 'text',
|
||||
key: 'advanced_1_1_1',
|
||||
required: true,
|
||||
validation: getDurationRt({ min: '1ms' }),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
it('returns false when form is invalid', () => {
|
||||
const vars: PackagePolicyVars = {
|
||||
foo: { value: undefined, type: 'text' },
|
||||
bar: { value: undefined, type: 'text' },
|
||||
baz: { value: 'baz', type: 'text' },
|
||||
advanced_1: { value: undefined, type: 'text' },
|
||||
advanced_1_1: { value: '1', type: 'text' },
|
||||
advanced_1_1_1: { value: undefined, type: 'text' },
|
||||
};
|
||||
expect(isSettingsFormValid(settings, vars)).toBeFalsy();
|
||||
});
|
||||
it('returns true when form is valid', () => {
|
||||
const vars: PackagePolicyVars = {
|
||||
foo: { value: 'foo', type: 'text' },
|
||||
bar: { value: undefined, type: 'text' },
|
||||
bar_1: { value: 'bar_1' },
|
||||
baz: { value: '1ms', type: 'text' },
|
||||
advanced_1: { value: 'advanced_1', type: 'text' },
|
||||
advanced_1_1: { value: undefined, type: 'text' },
|
||||
advanced_1_1_1: { value: '1s', type: 'text' },
|
||||
};
|
||||
expect(isSettingsFormValid(settings, vars)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
describe('mergeNewVars', () => {
|
||||
it('updates key value', () => {
|
||||
const vars: PackagePolicyVars = {
|
||||
foo: { value: 'foo', type: 'text' },
|
||||
bar: { value: undefined, type: 'text' },
|
||||
baz: { value: '1ms', type: 'text' },
|
||||
qux: { value: undefined, type: 'text' },
|
||||
};
|
||||
const newVars = mergeNewVars(vars, 'qux', 'qux');
|
||||
expect(newVars).toEqual({
|
||||
foo: { value: 'foo', type: 'text' },
|
||||
bar: { value: undefined, type: 'text' },
|
||||
baz: { value: '1ms', type: 'text' },
|
||||
qux: { value: 'qux', type: 'text' },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isRight } from 'fp-ts/lib/Either';
|
||||
import { PathReporter } from 'io-ts/lib/PathReporter';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { PackagePolicyVars } from '../typings';
|
||||
import { SettingDefinition, Setting } from './typings';
|
||||
|
||||
export const REQUIRED_LABEL = i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.requiredLabel',
|
||||
{ defaultMessage: 'Required' }
|
||||
);
|
||||
export const OPTIONAL_LABEL = i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.optionalLabel',
|
||||
{ defaultMessage: 'Optional' }
|
||||
);
|
||||
const REQUIRED_FIELD = i18n.translate(
|
||||
'xpack.apm.fleet_integration.settings.requiredFieldLabel',
|
||||
{ defaultMessage: 'Required field' }
|
||||
);
|
||||
|
||||
export function mergeNewVars(
|
||||
oldVars: PackagePolicyVars,
|
||||
key: string,
|
||||
value?: any
|
||||
): PackagePolicyVars {
|
||||
return { ...oldVars, [key]: { ...oldVars[key], value } };
|
||||
}
|
||||
|
||||
export function isSettingsFormValid(
|
||||
parentSettings: SettingDefinition[],
|
||||
vars: PackagePolicyVars
|
||||
) {
|
||||
function isSettingsValid(settings: SettingDefinition[]): boolean {
|
||||
return !settings
|
||||
.map((setting) => {
|
||||
if (setting.type === 'advanced_settings') {
|
||||
return isSettingsValid(setting.settings);
|
||||
}
|
||||
|
||||
if (setting.settings) {
|
||||
return isSettingsValid(setting.settings);
|
||||
}
|
||||
const { isValid } = validateSettingValue(
|
||||
setting,
|
||||
vars[setting.key]?.value
|
||||
);
|
||||
return isValid;
|
||||
})
|
||||
.flat()
|
||||
.some((isValid) => !isValid);
|
||||
}
|
||||
return isSettingsValid(parentSettings);
|
||||
}
|
||||
|
||||
export function validateSettingValue(setting: Setting, value?: any) {
|
||||
if (isEmpty(value)) {
|
||||
return {
|
||||
isValid: !setting.required,
|
||||
message: setting.required ? REQUIRED_FIELD : '',
|
||||
};
|
||||
}
|
||||
|
||||
if (setting.validation) {
|
||||
const result = setting.validation.decode(String(value));
|
||||
const message = PathReporter.report(result)[0];
|
||||
const isValid = isRight(result);
|
||||
return { isValid, message };
|
||||
}
|
||||
return { isValid: true, message: '' };
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { PackagePolicyConfigRecordEntry } from '../../../../../fleet/common';
|
||||
|
||||
export {
|
||||
PackagePolicyCreateExtensionComponentProps,
|
||||
PackagePolicyEditExtensionComponentProps,
|
||||
} from '../../../../../fleet/public';
|
||||
|
||||
export {
|
||||
NewPackagePolicy,
|
||||
PackagePolicy,
|
||||
PackagePolicyConfigRecordEntry,
|
||||
} from '../../../../../fleet/common';
|
||||
|
||||
export type PackagePolicyVars = Record<string, PackagePolicyConfigRecordEntry>;
|
||||
|
||||
export type OnFormChangeFn = (
|
||||
newVars: PackagePolicyVars,
|
||||
isValid: boolean
|
||||
) => void;
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
import { PackagePolicyCreateExtensionComponent } from '../../../../fleet/public';
|
||||
|
||||
export const getLazyAPMPolicyCreateExtension = () => {
|
||||
return lazy<PackagePolicyCreateExtensionComponent>(async () => {
|
||||
const { CreateAPMPolicyForm } = await import(
|
||||
'./apm_policy_form/create_apm_policy_form'
|
||||
);
|
||||
|
||||
return { default: CreateAPMPolicyForm };
|
||||
});
|
||||
};
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
import { PackagePolicyEditExtensionComponent } from '../../../../fleet/public';
|
||||
|
||||
export const getLazyAPMPolicyEditExtension = () => {
|
||||
return lazy<PackagePolicyEditExtensionComponent>(async () => {
|
||||
const { EditAPMPolicyForm } = await import(
|
||||
'./apm_policy_form/edit_apm_policy_form'
|
||||
);
|
||||
|
||||
return { default: EditAPMPolicyForm };
|
||||
});
|
||||
};
|
|
@ -48,6 +48,8 @@ import {
|
|||
getApmEnrollmentFlyoutData,
|
||||
LazyApmCustomAssetsExtension,
|
||||
} from './components/fleet_integration';
|
||||
import { getLazyAPMPolicyCreateExtension } from './components/fleet_integration/lazy_apm_policy_create_extension';
|
||||
import { getLazyAPMPolicyEditExtension } from './components/fleet_integration/lazy_apm_policy_edit_extension';
|
||||
|
||||
export type ApmPluginSetup = ReturnType<ApmPlugin['setup']>;
|
||||
|
||||
|
@ -332,6 +334,18 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
|||
view: 'package-detail-assets',
|
||||
Component: LazyApmCustomAssetsExtension,
|
||||
});
|
||||
|
||||
fleet.registerExtension({
|
||||
package: 'apm',
|
||||
view: 'package-policy-create',
|
||||
Component: getLazyAPMPolicyCreateExtension(),
|
||||
});
|
||||
|
||||
fleet.registerExtension({
|
||||
package: 'apm',
|
||||
view: 'package-policy-edit',
|
||||
Component: getLazyAPMPolicyEditExtension(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,9 @@
|
|||
import { Story } from '@storybook/react';
|
||||
import { HttpStart } from 'kibana/public';
|
||||
import React from 'react';
|
||||
import { POLICY_ELASTIC_AGENT_ON_CLOUD } from '../../../common/fleet';
|
||||
import TutorialConfigAgent from './';
|
||||
import { APIReturnType } from '../..//services/rest/createCallApmApi';
|
||||
import { APIReturnType } from '../../services/rest/createCallApmApi';
|
||||
|
||||
export type APIResponseType = APIReturnType<'GET /api/apm/fleet/agents'>;
|
||||
|
||||
|
@ -22,7 +23,7 @@ interface Args {
|
|||
}
|
||||
|
||||
const policyElasticAgentOnCloudAgent: APIResponseType['fleetAgents'][0] = {
|
||||
id: 'policy-elastic-agent-on-cloud',
|
||||
id: POLICY_ELASTIC_AGENT_ON_CLOUD,
|
||||
name: 'Elastic Cloud agent policy',
|
||||
apmServerUrl: 'apm_cloud_url',
|
||||
secretToken: 'apm_cloud_token',
|
||||
|
|
|
@ -5,10 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { POLICY_ELASTIC_AGENT_ON_CLOUD } from '../../../common/fleet';
|
||||
import { APIResponseType } from './';
|
||||
|
||||
const POLICY_ELASTIC_AGENT_ON_CLOUD = 'policy-elastic-agent-on-cloud';
|
||||
|
||||
const DEFAULT_STANDALONE_CONFIG_LABEL = i18n.translate(
|
||||
'xpack.apm.tutorial.agent_config.defaultStandaloneConfig',
|
||||
{ defaultMessage: 'Default Standalone configuration' }
|
||||
|
|
|
@ -5,11 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { POLICY_ELASTIC_AGENT_ON_CLOUD } from '../../../common/fleet';
|
||||
import { APMPluginSetupDependencies } from '../../types';
|
||||
import {
|
||||
POLICY_ELASTIC_AGENT_ON_CLOUD,
|
||||
APM_PACKAGE_NAME,
|
||||
} from './get_cloud_apm_package_policy';
|
||||
import { APM_PACKAGE_NAME } from './get_cloud_apm_package_policy';
|
||||
|
||||
interface GetApmPackagePolicyDefinitionOptions {
|
||||
apmServerSchema: Record<string, any>;
|
||||
|
|
|
@ -9,8 +9,8 @@ import { SavedObjectsClientContract } from 'kibana/server';
|
|||
import { Maybe } from '../../../typings/common';
|
||||
import { AgentPolicy, PackagePolicy } from '../../../../fleet/common';
|
||||
import { APMPluginStartDependencies } from '../../types';
|
||||
import { POLICY_ELASTIC_AGENT_ON_CLOUD } from '../../../common/fleet';
|
||||
|
||||
export const POLICY_ELASTIC_AGENT_ON_CLOUD = 'policy-elastic-agent-on-cloud';
|
||||
export const APM_PACKAGE_NAME = 'apm';
|
||||
|
||||
export async function getCloudAgentPolicy({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue