mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Fleet] Settings Framework API and UI (#179795)
## Summary Follow up on https://github.com/elastic/kibana/pull/170539 Related to https://github.com/elastic/ingest-dev/issues/2471 (Phase 1) Dynamically creating settings fields from configuration. These settings are saved in the agent policy SO's `advanced_settings` field. In the current pr the agent policy read/create/update works including the UI. It still has to be extended to support a few more type of settings: e.g. dropdown values, settings consisting of multiple fields. <img width="2212" alt="image" src="c2ee7187
-41bf-42a4-8a22-f43ea8ccfb8c"> These settings are added to the full agent policy agents section: <img width="687" alt="image" src="05e244f3
-148c-4c88-9b9c-fd665fd0154c"> ## Old description: Add support for saved object mapping and api field name, ## Example API calls and full policy generation <img width="600" alt="Screenshot 2024-04-02 at 3 54 35 PM" src="ee2ea087
-3e02-4351-9138-c56ace1d5b34"> <img width="500" alt="Screenshot 2024-04-02 at 4 13 42 PM" src="97514b2e
-ef39-475e-8a88-1204ce2c0cda"> <img width="600" alt="Screenshot 2024-04-02 at 4 13 54 PM" src="6553e133
-ea07-48b5-b0ec-c45861b9b246"> <img width="600" alt="Screenshot 2024-04-02 at 5 42 27 PM" src="faa560fd
-7303-46f5-ae2e-034fc10dddfd"> ## Open questions/Issues ### Saved objects *I think we will still have to do some work to add a new model version when adding a new saved object field, I do not see an easy way to programatically generate that. In a first time it probably could be a manual action to add those migration ### API Open api generation, I think as a first iteration it could be a manual operation to update openAPI spec, but we should be able to programatically generate that with a script in the future --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Co-authored-by: Julia Bardi <julia.bardi@elastic.co>
This commit is contained in:
parent
f7ebd29b33
commit
c88b3bdc95
25 changed files with 785 additions and 52 deletions
|
@ -180,7 +180,7 @@ export const HASH_TO_VERSION_MAP = {
|
|||
'infrastructure-monitoring-log-view|c50526fc6040c5355ed027d34d05b35c': '10.0.0',
|
||||
'infrastructure-ui-source|3d1b76c39bfb2cc8296b024d73854724': '10.0.0',
|
||||
'ingest_manager_settings|b91ffb075799c78ffd7dbd51a279c8c9': '10.1.0',
|
||||
'ingest-agent-policies|20768dc7ce5eced3eb309e50d8a6cf76': '10.0.0',
|
||||
'ingest-agent-policies|0fd93cd11c019b118e93a9157c22057b': '10.1.0',
|
||||
'ingest-download-sources|0b0f6828e59805bd07a650d80817c342': '10.0.0',
|
||||
'ingest-outputs|b1237f7fdc0967709e75d65d208ace05': '10.6.0',
|
||||
'ingest-package-policies|a1a074bad36e68d54f98d2158d60f879': '10.0.0',
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
],
|
||||
"infrastructure-ui-source": [],
|
||||
"ingest-agent-policies": [
|
||||
"advanced_settings",
|
||||
"agent_features",
|
||||
"agent_features.enabled",
|
||||
"agent_features.name",
|
||||
|
|
|
@ -1590,6 +1590,10 @@
|
|||
},
|
||||
"ingest-agent-policies": {
|
||||
"properties": {
|
||||
"advanced_settings": {
|
||||
"index": false,
|
||||
"type": "flattened"
|
||||
},
|
||||
"agent_features": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
|
|
|
@ -106,7 +106,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"infra-custom-dashboards": "1a5994f2e05bb8a1609825ddbf5012f77c5c67f3",
|
||||
"infrastructure-monitoring-log-view": "5f86709d3c27aed7a8379153b08ee5d3d90d77f5",
|
||||
"infrastructure-ui-source": "113182d6895764378dfe7fa9fa027244f3a457c4",
|
||||
"ingest-agent-policies": "7633e578f60c074f8267bc50ec4763845e431437",
|
||||
"ingest-agent-policies": "d2ee0bf36a512c2ac744b0def1c822b7880f1f83",
|
||||
"ingest-download-sources": "279a68147e62e4d8858c09ad1cf03bd5551ce58d",
|
||||
"ingest-outputs": "daafff49255ab700e07491376fe89f04fc998b91",
|
||||
"ingest-package-policies": "8a99e165aab00c6c365540427a3abeb7bea03f31",
|
||||
|
|
|
@ -29,6 +29,7 @@ export const allowedExperimentalValues = Object.freeze<Record<string, boolean>>(
|
|||
enableStrictKQLValidation: false,
|
||||
subfeaturePrivileges: false,
|
||||
enablePackagesStateMachine: true,
|
||||
advancedPolicySettings: true,
|
||||
});
|
||||
|
||||
type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
|
||||
|
|
|
@ -7383,6 +7383,11 @@
|
|||
"type": "object",
|
||||
"description": "Override settings that are defined in the agent policy. Input settings cannot be overridden. The override option should be used only in unusual circumstances and not as a routine procedure.",
|
||||
"nullable": true
|
||||
},
|
||||
"advanced_settings": {
|
||||
"type": "object",
|
||||
"description": "Advanced settings stored in the agent policy, e.g. agent_limits_go_max_procs",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
@ -4734,6 +4734,12 @@ components:
|
|||
settings cannot be overridden. The override option should be used
|
||||
only in unusual circumstances and not as a routine procedure.
|
||||
nullable: true
|
||||
advanced_settings:
|
||||
type: object
|
||||
description: >-
|
||||
Advanced settings stored in the agent policy, e.g.
|
||||
agent_limits_go_max_procs
|
||||
nullable: true
|
||||
required:
|
||||
- id
|
||||
- status
|
||||
|
|
|
@ -69,6 +69,10 @@ properties:
|
|||
type: object
|
||||
description: Override settings that are defined in the agent policy. Input settings cannot be overridden. The override option should be used only in unusual circumstances and not as a routine procedure.
|
||||
nullable: true
|
||||
advanced_settings:
|
||||
type: object
|
||||
description: Advanced settings stored in the agent policy, e.g. agent_limits_go_max_procs
|
||||
nullable: true
|
||||
required:
|
||||
- id
|
||||
- status
|
||||
|
|
100
x-pack/plugins/fleet/common/settings/agent_policy_settings.ts
Normal file
100
x-pack/plugins/fleet/common/settings/agent_policy_settings.ts
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 { z } from 'zod';
|
||||
|
||||
import type { SettingsConfig } from './types';
|
||||
|
||||
export const zodStringWithDurationValidation = z
|
||||
.string()
|
||||
.refine((val) => val.match(/^(\d+[s|m|h|d])?$/), {
|
||||
message: i18n.translate(
|
||||
'xpack.fleet.settings.agentPolicyAdvanced.downloadTimeoutValidationMessage',
|
||||
{
|
||||
defaultMessage: 'Must be a string with a time unit, e.g. 30s, 5m, 2h, 1d',
|
||||
}
|
||||
),
|
||||
});
|
||||
|
||||
export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [
|
||||
{
|
||||
name: 'agent.limits.go_max_procs',
|
||||
title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.goMaxProcsTitle', {
|
||||
defaultMessage: 'GO_MAX_PROCS',
|
||||
}),
|
||||
description: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.goMaxProcsDescription', {
|
||||
defaultMessage: 'Limits the maximum number of CPUs that can be executing simultaneously',
|
||||
}),
|
||||
learnMoreLink:
|
||||
'https://www.elastic.co/guide/en/fleet/current/enable-custom-policy-settings.html#limit-cpu-usage',
|
||||
api_field: {
|
||||
name: 'agent_limits_go_max_procs',
|
||||
},
|
||||
schema: z.number().int().min(0).default(0),
|
||||
},
|
||||
{
|
||||
name: 'agent.download.timeout',
|
||||
title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.downloadTimeoutTitle', {
|
||||
defaultMessage: 'Agent binary download timeout',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'xpack.fleet.settings.agentPolicyAdvanced.downloadTimeoutDescription',
|
||||
{
|
||||
defaultMessage: 'Timeout in seconds for downloading the agent binary',
|
||||
}
|
||||
),
|
||||
learnMoreLink:
|
||||
'https://www.elastic.co/guide/en/fleet/current/enable-custom-policy-settings.html#configure-agent-download-timeout',
|
||||
api_field: {
|
||||
name: 'agent_download_timeout',
|
||||
},
|
||||
schema: zodStringWithDurationValidation.default('120s'),
|
||||
},
|
||||
{
|
||||
name: 'agent.download.target_directory',
|
||||
api_field: {
|
||||
name: 'agent_download_target_directory',
|
||||
},
|
||||
title: i18n.translate(
|
||||
'xpack.fleet.settings.agentPolicyAdvanced.agentDownloadTargetDirectoryTitle',
|
||||
{
|
||||
defaultMessage: 'Agent binary target directory',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'xpack.fleet.settings.agentPolicyAdvanced.agentDownloadTargetDirectoryDescription',
|
||||
{
|
||||
defaultMessage: 'The disk path to which the agent binary will be downloaded',
|
||||
}
|
||||
),
|
||||
learnMoreLink:
|
||||
'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-download.html',
|
||||
schema: z.string(),
|
||||
},
|
||||
{
|
||||
name: 'agent.logging.metrics.period',
|
||||
api_field: {
|
||||
name: 'agent_logging_metrics_period',
|
||||
},
|
||||
title: i18n.translate(
|
||||
'xpack.fleet.settings.agentPolicyAdvanced.agentLoggingMetricsPeriodTitle',
|
||||
{
|
||||
defaultMessage: 'Agent logging metrics period',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'xpack.fleet.settings.agentPolicyAdvanced.agentLoggingMetricsPeriodDescription',
|
||||
{
|
||||
defaultMessage: 'The frequency of agent metrics logging',
|
||||
}
|
||||
),
|
||||
learnMoreLink:
|
||||
'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings',
|
||||
schema: zodStringWithDurationValidation.default('30s'),
|
||||
},
|
||||
];
|
8
x-pack/plugins/fleet/common/settings/index.ts
Normal file
8
x-pack/plugins/fleet/common/settings/index.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 { AGENT_POLICY_ADVANCED_SETTINGS } from './agent_policy_settings';
|
21
x-pack/plugins/fleet/common/settings/types.ts
Normal file
21
x-pack/plugins/fleet/common/settings/types.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 type { z } from 'zod';
|
||||
|
||||
export type SettingsSection = 'AGENT_POLICY_ADVANCED_SETTINGS';
|
||||
|
||||
export interface SettingsConfig {
|
||||
name: string;
|
||||
title: string;
|
||||
description: string;
|
||||
learnMoreLink?: string;
|
||||
schema: z.ZodTypeAny;
|
||||
api_field: {
|
||||
name: string;
|
||||
};
|
||||
}
|
|
@ -39,6 +39,7 @@ export interface NewAgentPolicy {
|
|||
agent_features?: Array<{ name: string; enabled: boolean }>;
|
||||
is_protected?: boolean;
|
||||
overrides?: { [key: string]: any } | null;
|
||||
advanced_settings?: { [key: string]: any } | null;
|
||||
}
|
||||
|
||||
// SO definition for this type is declared in server/types/interfaces
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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 { act, fireEvent } from '@testing-library/react';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { zodStringWithDurationValidation } from '../../../../../common/settings/agent_policy_settings';
|
||||
import type { SettingsConfig } from '../../../../../common/settings/types';
|
||||
import { createFleetTestRendererMock } from '../../../../mock';
|
||||
|
||||
import { ConfiguredSettings } from '.';
|
||||
|
||||
const mockUpdateAgentPolicy = jest.fn();
|
||||
const mockUpdateAdvancedSettingsHasErrors = jest.fn();
|
||||
|
||||
jest.mock('../../sections/agent_policy/components/agent_policy_form', () => ({
|
||||
useAgentPolicyFormContext: () => ({
|
||||
updateAdvancedSettingsHasErrors: mockUpdateAdvancedSettingsHasErrors,
|
||||
updateAgentPolicy: mockUpdateAgentPolicy,
|
||||
agentPolicy: {
|
||||
advanced_settings: {
|
||||
agent_limits_go_max_procs: 0,
|
||||
agent_download_timeout: '120s',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('ConfiguredSettings', () => {
|
||||
const testRenderer = createFleetTestRendererMock();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
function render(settingsConfig: SettingsConfig[]) {
|
||||
return testRenderer.render(<ConfiguredSettings configuredSettings={settingsConfig} />);
|
||||
}
|
||||
|
||||
it('should render number field', () => {
|
||||
const result = render([
|
||||
{
|
||||
name: 'agent.limits.go_max_procs',
|
||||
title: 'GO_MAX_PROCS',
|
||||
description: 'Description',
|
||||
learnMoreLink: '',
|
||||
api_field: {
|
||||
name: 'agent_limits_go_max_procs',
|
||||
},
|
||||
schema: z.number().int().min(0).default(0),
|
||||
},
|
||||
]);
|
||||
|
||||
expect(result.getByText('GO_MAX_PROCS')).not.toBeNull();
|
||||
const input = result.getByTestId('configuredSetting-agent.limits.go_max_procs');
|
||||
expect(input).toHaveValue(0);
|
||||
|
||||
act(() => {
|
||||
fireEvent.change(input, { target: { value: '1' } });
|
||||
});
|
||||
|
||||
expect(mockUpdateAgentPolicy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
advanced_settings: expect.objectContaining({ agent_limits_go_max_procs: 1 }),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should render string field with time duration validation', () => {
|
||||
const result = render([
|
||||
{
|
||||
name: 'agent.download.timeout',
|
||||
title: 'Agent binary download timeout',
|
||||
description: 'Description',
|
||||
learnMoreLink: '',
|
||||
api_field: {
|
||||
name: 'agent_download_timeout',
|
||||
},
|
||||
schema: zodStringWithDurationValidation.default('120s'),
|
||||
},
|
||||
]);
|
||||
|
||||
expect(result.getByText('Agent binary download timeout')).not.toBeNull();
|
||||
const input = result.getByTestId('configuredSetting-agent.download.timeout');
|
||||
expect(input).toHaveValue('120s');
|
||||
|
||||
act(() => {
|
||||
fireEvent.change(input, { target: { value: '120' } });
|
||||
});
|
||||
|
||||
expect(input).toHaveAttribute('aria-invalid', 'true');
|
||||
expect(
|
||||
result.getByText('Must be a string with a time unit, e.g. 30s, 5m, 2h, 1d')
|
||||
).not.toBeNull();
|
||||
expect(mockUpdateAdvancedSettingsHasErrors).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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 { z, ZodFirstPartyTypeKind } from 'zod';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
EuiDescribedFormGroup,
|
||||
EuiFieldNumber,
|
||||
EuiFieldText,
|
||||
EuiFormRow,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import type { SettingsConfig } from '../../../../../common/settings/types';
|
||||
import { useAgentPolicyFormContext } from '../../sections/agent_policy/components/agent_policy_form';
|
||||
|
||||
export const settingComponentRegistry = new Map<
|
||||
string,
|
||||
(settingsconfig: SettingsConfig) => React.ReactElement
|
||||
>();
|
||||
|
||||
settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodNumber, (settingsConfig) => {
|
||||
return (
|
||||
<SettingsFieldWrapper
|
||||
settingsConfig={settingsConfig}
|
||||
typeName={ZodFirstPartyTypeKind.ZodNumber}
|
||||
renderItem={({ fieldKey, fieldValue, handleChange, isInvalid, coercedSchema }: any) => (
|
||||
<EuiFieldNumber
|
||||
fullWidth
|
||||
data-test-subj={fieldKey}
|
||||
value={fieldValue}
|
||||
onChange={handleChange}
|
||||
isInvalid={isInvalid}
|
||||
min={coercedSchema.minValue ?? undefined}
|
||||
max={coercedSchema.maxValue ?? undefined}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodString, (settingsConfig) => {
|
||||
return (
|
||||
<SettingsFieldWrapper
|
||||
settingsConfig={settingsConfig}
|
||||
typeName={ZodFirstPartyTypeKind.ZodString}
|
||||
renderItem={({ fieldKey, fieldValue, handleChange, isInvalid }: any) => (
|
||||
<EuiFieldText
|
||||
fullWidth
|
||||
data-test-subj={fieldKey}
|
||||
value={fieldValue}
|
||||
onChange={handleChange}
|
||||
isInvalid={isInvalid}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const SettingsFieldWrapper: React.FC<{
|
||||
settingsConfig: SettingsConfig;
|
||||
typeName: keyof typeof ZodFirstPartyTypeKind;
|
||||
renderItem: Function;
|
||||
}> = ({ settingsConfig, typeName, renderItem }) => {
|
||||
const [error, setError] = useState('');
|
||||
const agentPolicyFormContext = useAgentPolicyFormContext();
|
||||
|
||||
const fieldKey = `configuredSetting-${settingsConfig.name}`;
|
||||
const defaultValue: number =
|
||||
settingsConfig.schema instanceof z.ZodDefault
|
||||
? settingsConfig.schema._def.defaultValue()
|
||||
: undefined;
|
||||
const coercedSchema = settingsConfig.schema as z.ZodString;
|
||||
|
||||
const convertValue = (value: string, type: keyof typeof ZodFirstPartyTypeKind): any => {
|
||||
if (type === ZodFirstPartyTypeKind.ZodNumber) {
|
||||
if (value === '') {
|
||||
return 0;
|
||||
}
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = convertValue(e.target.value, typeName);
|
||||
const validationResults = coercedSchema.safeParse(newValue);
|
||||
|
||||
if (!validationResults.success) {
|
||||
setError(validationResults.error.issues[0].message);
|
||||
agentPolicyFormContext?.updateAdvancedSettingsHasErrors(true);
|
||||
} else {
|
||||
setError('');
|
||||
agentPolicyFormContext?.updateAdvancedSettingsHasErrors(false);
|
||||
}
|
||||
|
||||
const newAdvancedSettings = {
|
||||
...(agentPolicyFormContext?.agentPolicy.advanced_settings ?? {}),
|
||||
[settingsConfig.api_field.name]: newValue,
|
||||
};
|
||||
|
||||
agentPolicyFormContext?.updateAgentPolicy({ advanced_settings: newAdvancedSettings });
|
||||
};
|
||||
|
||||
const fieldValue =
|
||||
agentPolicyFormContext?.agentPolicy.advanced_settings?.[settingsConfig.api_field.name] ??
|
||||
defaultValue;
|
||||
|
||||
return (
|
||||
<EuiDescribedFormGroup
|
||||
fullWidth
|
||||
title={<h4>{settingsConfig.title}</h4>}
|
||||
description={
|
||||
<>
|
||||
{settingsConfig.description}.{' '}
|
||||
<EuiLink href={settingsConfig.learnMoreLink} external>
|
||||
Learn more.
|
||||
</EuiLink>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<EuiFormRow fullWidth key={fieldKey} error={error} isInvalid={!!error}>
|
||||
{renderItem({ fieldValue, handleChange, isInvalid: !!error, fieldKey, coercedSchema })}
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export function ConfiguredSettings({
|
||||
configuredSettings,
|
||||
}: {
|
||||
configuredSettings: SettingsConfig[];
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{configuredSettings.map((configuredSetting) => {
|
||||
const Component = settingComponentRegistry.get(
|
||||
configuredSetting.schema instanceof z.ZodDefault
|
||||
? configuredSetting.schema._def.innerType._def.typeName === 'ZodEffects'
|
||||
? configuredSetting.schema._def.innerType._def.schema._def.typeName
|
||||
: configuredSetting.schema._def.innerType._def.typeName
|
||||
: configuredSetting.schema._def.typeName
|
||||
);
|
||||
|
||||
if (!Component) {
|
||||
throw new Error(`Unknown setting type: ${configuredSetting.schema._type}}`);
|
||||
}
|
||||
|
||||
return <Component key={configuredSetting.name} {...configuredSetting} />;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -12,13 +12,19 @@ import {
|
|||
EuiForm,
|
||||
EuiHorizontalRule,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { AGENT_POLICY_ADVANCED_SETTINGS } from '../../../../../../common/settings';
|
||||
import type { NewAgentPolicy, AgentPolicy } from '../../../types';
|
||||
import { useAuthz } from '../../../../../hooks';
|
||||
|
||||
import { ConfiguredSettings } from '../../../components/form_settings';
|
||||
|
||||
import { ExperimentalFeaturesService } from '../../../../../services';
|
||||
|
||||
import { AgentPolicyAdvancedOptionsContent } from './agent_policy_advanced_fields';
|
||||
import { AgentPolicyGeneralFields } from './agent_policy_general_fields';
|
||||
import { AgentPolicyFormSystemMonitoringCheckbox } from './agent_policy_system_monitoring_field';
|
||||
|
@ -37,7 +43,21 @@ interface Props {
|
|||
updateSysMonitoring: (newValue: boolean) => void;
|
||||
validation: ValidationResults;
|
||||
isEditing?: boolean;
|
||||
// form error state is passed up to the form
|
||||
updateAdvancedSettingsHasErrors: (hasErrors: boolean) => void;
|
||||
}
|
||||
const AgentPolicyFormContext = React.createContext<
|
||||
| {
|
||||
agentPolicy: Partial<NewAgentPolicy | AgentPolicy> & { [key: string]: any };
|
||||
updateAgentPolicy: (u: Partial<NewAgentPolicy | AgentPolicy>) => void;
|
||||
updateAdvancedSettingsHasErrors: (hasErrors: boolean) => void;
|
||||
}
|
||||
| undefined
|
||||
>(undefined);
|
||||
|
||||
export const useAgentPolicyFormContext = () => {
|
||||
return React.useContext(AgentPolicyFormContext);
|
||||
};
|
||||
|
||||
export const AgentPolicyForm: React.FunctionComponent<Props> = ({
|
||||
agentPolicy,
|
||||
|
@ -46,10 +66,13 @@ export const AgentPolicyForm: React.FunctionComponent<Props> = ({
|
|||
updateSysMonitoring,
|
||||
validation,
|
||||
isEditing = false,
|
||||
updateAdvancedSettingsHasErrors,
|
||||
}) => {
|
||||
const authz = useAuthz();
|
||||
const disabled = !authz.fleet.allAgents;
|
||||
|
||||
const { advancedPolicySettings } = ExperimentalFeaturesService.get();
|
||||
|
||||
const generalSettingsWrapper = (children: JSX.Element[]) => (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
|
@ -72,62 +95,102 @@ export const AgentPolicyForm: React.FunctionComponent<Props> = ({
|
|||
);
|
||||
|
||||
return (
|
||||
<EuiForm>
|
||||
{!isEditing ? (
|
||||
<AgentPolicyGeneralFields
|
||||
agentPolicy={agentPolicy}
|
||||
updateAgentPolicy={updateAgentPolicy}
|
||||
validation={validation}
|
||||
disabled={disabled}
|
||||
/>
|
||||
) : (
|
||||
generalSettingsWrapper([
|
||||
<AgentPolicyFormContext.Provider
|
||||
value={{ agentPolicy, updateAgentPolicy, updateAdvancedSettingsHasErrors }}
|
||||
>
|
||||
<EuiForm>
|
||||
{!isEditing ? (
|
||||
<AgentPolicyGeneralFields
|
||||
agentPolicy={agentPolicy}
|
||||
updateAgentPolicy={updateAgentPolicy}
|
||||
validation={validation}
|
||||
disabled={disabled}
|
||||
/>,
|
||||
])
|
||||
)}
|
||||
{!isEditing ? (
|
||||
<AgentPolicyFormSystemMonitoringCheckbox
|
||||
withSysMonitoring={withSysMonitoring}
|
||||
updateSysMonitoring={updateSysMonitoring}
|
||||
/>
|
||||
) : null}
|
||||
{!isEditing ? (
|
||||
<>
|
||||
<EuiHorizontalRule />
|
||||
<EuiSpacer size="xs" />
|
||||
<StyledEuiAccordion
|
||||
id="advancedOptions"
|
||||
buttonContent={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.advancedOptionsToggleLabel"
|
||||
defaultMessage="Advanced options"
|
||||
/>
|
||||
) : (
|
||||
generalSettingsWrapper([
|
||||
<AgentPolicyGeneralFields
|
||||
agentPolicy={agentPolicy}
|
||||
updateAgentPolicy={updateAgentPolicy}
|
||||
validation={validation}
|
||||
disabled={disabled}
|
||||
/>,
|
||||
])
|
||||
)}
|
||||
{!isEditing ? (
|
||||
<AgentPolicyFormSystemMonitoringCheckbox
|
||||
withSysMonitoring={withSysMonitoring}
|
||||
updateSysMonitoring={updateSysMonitoring}
|
||||
/>
|
||||
) : null}
|
||||
{!isEditing ? (
|
||||
<>
|
||||
<EuiHorizontalRule />
|
||||
<EuiSpacer size="xs" />
|
||||
<StyledEuiAccordion
|
||||
id="advancedOptions"
|
||||
buttonContent={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.advancedOptionsToggleLabel"
|
||||
defaultMessage="Advanced options"
|
||||
/>
|
||||
}
|
||||
buttonClassName="ingest-active-button"
|
||||
>
|
||||
<EuiSpacer size="l" />
|
||||
<AgentPolicyAdvancedOptionsContent
|
||||
agentPolicy={agentPolicy}
|
||||
updateAgentPolicy={updateAgentPolicy}
|
||||
validation={validation}
|
||||
isEditing={isEditing}
|
||||
/>
|
||||
}
|
||||
buttonClassName="ingest-active-button"
|
||||
>
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
{advancedPolicySettings ? (
|
||||
<>
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.advancedSettingsTitle"
|
||||
defaultMessage="Advanced settings"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<ConfiguredSettings configuredSettings={AGENT_POLICY_ADVANCED_SETTINGS} />
|
||||
</>
|
||||
) : null}
|
||||
</StyledEuiAccordion>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<AgentPolicyAdvancedOptionsContent
|
||||
agentPolicy={agentPolicy}
|
||||
updateAgentPolicy={updateAgentPolicy}
|
||||
validation={validation}
|
||||
isEditing={isEditing}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</StyledEuiAccordion>
|
||||
</>
|
||||
) : (
|
||||
<AgentPolicyAdvancedOptionsContent
|
||||
agentPolicy={agentPolicy}
|
||||
updateAgentPolicy={updateAgentPolicy}
|
||||
validation={validation}
|
||||
isEditing={isEditing}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)}
|
||||
</EuiForm>
|
||||
{advancedPolicySettings ? (
|
||||
<>
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.advancedSettingsTitle"
|
||||
defaultMessage="Advanced settings"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<ConfiguredSettings configuredSettings={AGENT_POLICY_ADVANCED_SETTINGS} />
|
||||
</>
|
||||
) : null}
|
||||
<EuiSpacer size="xl" />
|
||||
</>
|
||||
)}
|
||||
</EuiForm>
|
||||
</AgentPolicyFormContext.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -52,6 +52,7 @@ const pickAgentPolicyKeysToSend = (agentPolicy: AgentPolicy) =>
|
|||
'fleet_server_host_id',
|
||||
'agent_features',
|
||||
'is_protected',
|
||||
'advanced_settings',
|
||||
]);
|
||||
|
||||
const FormWrapper = styled.div`
|
||||
|
@ -77,6 +78,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>(
|
|||
const [agentCount, setAgentCount] = useState<number>(0);
|
||||
const [withSysMonitoring, setWithSysMonitoring] = useState<boolean>(true);
|
||||
const validation = agentPolicyFormValidation(agentPolicy);
|
||||
const [hasAdvancedSettingsErrors, setHasAdvancedSettingsErrors] = useState<boolean>(false);
|
||||
|
||||
const updateAgentPolicy = (updatedFields: Partial<AgentPolicy>) => {
|
||||
setAgentPolicy({
|
||||
|
@ -169,6 +171,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>(
|
|||
updateSysMonitoring={(newValue) => setWithSysMonitoring(newValue)}
|
||||
validation={validation}
|
||||
isEditing={true}
|
||||
updateAdvancedSettingsHasErrors={setHasAdvancedSettingsErrors}
|
||||
/>
|
||||
|
||||
{hasChanges ? (
|
||||
|
@ -202,7 +205,11 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>(
|
|||
{showDevtoolsRequest ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<DevtoolsRequestFlyoutButton
|
||||
isDisabled={isLoading || Object.keys(validation).length > 0}
|
||||
isDisabled={
|
||||
isLoading ||
|
||||
Object.keys(validation).length > 0 ||
|
||||
hasAdvancedSettingsErrors
|
||||
}
|
||||
btnProps={{
|
||||
color: 'text',
|
||||
}}
|
||||
|
@ -221,7 +228,10 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>(
|
|||
onClick={onSubmit}
|
||||
isLoading={isLoading}
|
||||
isDisabled={
|
||||
!hasFleetAllPrivileges || isLoading || Object.keys(validation).length > 0
|
||||
!hasFleetAllPrivileges ||
|
||||
isLoading ||
|
||||
Object.keys(validation).length > 0 ||
|
||||
hasAdvancedSettingsErrors
|
||||
}
|
||||
iconType="save"
|
||||
color="primary"
|
||||
|
|
|
@ -53,6 +53,7 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent<Props> = ({
|
|||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [withSysMonitoring, setWithSysMonitoring] = useState<boolean>(true);
|
||||
const validation = agentPolicyFormValidation(agentPolicy);
|
||||
const [hasAdvancedSettingsErrors, setHasAdvancedSettingsErrors] = useState<boolean>(false);
|
||||
|
||||
const updateAgentPolicy = (updatedFields: Partial<NewAgentPolicy>) => {
|
||||
setAgentPolicy({
|
||||
|
@ -95,6 +96,7 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent<Props> = ({
|
|||
withSysMonitoring={withSysMonitoring}
|
||||
updateSysMonitoring={(newValue) => setWithSysMonitoring(newValue)}
|
||||
validation={validation}
|
||||
updateAdvancedSettingsHasErrors={setHasAdvancedSettingsErrors}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
);
|
||||
|
@ -120,7 +122,9 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent<Props> = ({
|
|||
{showDevtoolsRequest ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<DevtoolsRequestFlyoutButton
|
||||
isDisabled={isLoading || Object.keys(validation).length > 0}
|
||||
isDisabled={
|
||||
isLoading || Object.keys(validation).length > 0 || hasAdvancedSettingsErrors
|
||||
}
|
||||
description={i18n.translate(
|
||||
'xpack.fleet.createAgentPolicy.devtoolsRequestDescription',
|
||||
{
|
||||
|
@ -136,7 +140,10 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent<Props> = ({
|
|||
fill
|
||||
isLoading={isLoading}
|
||||
isDisabled={
|
||||
!hasFleetAllPrivileges || isLoading || Object.keys(validation).length > 0
|
||||
!hasFleetAllPrivileges ||
|
||||
isLoading ||
|
||||
Object.keys(validation).length > 0 ||
|
||||
hasAdvancedSettingsErrors
|
||||
}
|
||||
onClick={async () => {
|
||||
setIsLoading(true);
|
||||
|
|
|
@ -156,6 +156,7 @@ export const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({
|
|||
is_protected: { type: 'boolean' },
|
||||
overrides: { type: 'flattened', index: false },
|
||||
keep_monitoring_alive: { type: 'boolean' },
|
||||
advanced_settings: { type: 'flattened', index: false },
|
||||
},
|
||||
},
|
||||
migrations: {
|
||||
|
@ -165,6 +166,18 @@ export const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({
|
|||
'8.5.0': migrateAgentPolicyToV850,
|
||||
'8.9.0': migrateAgentPolicyToV890,
|
||||
},
|
||||
modelVersions: {
|
||||
'1': {
|
||||
changes: [
|
||||
{
|
||||
type: 'mappings_addition',
|
||||
addedMappings: {
|
||||
advanced_settings: { type: 'flattened', index: false },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[OUTPUT_SAVED_OBJECT_TYPE]: {
|
||||
name: OUTPUT_SAVED_OBJECT_TYPE,
|
||||
|
|
|
@ -714,6 +714,30 @@ describe('getFullAgentPolicy', () => {
|
|||
revision: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a policy with advanced settings', async () => {
|
||||
mockAgentPolicy({
|
||||
advanced_settings: {
|
||||
agent_limits_go_max_procs: 2,
|
||||
agent_download_timeout: '60s',
|
||||
agent_download_target_directory: '/tmp',
|
||||
agent_logging_metrics_period: '10s',
|
||||
},
|
||||
});
|
||||
const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy');
|
||||
|
||||
expect(agentPolicy).toMatchObject({
|
||||
id: 'agent-policy',
|
||||
agent: {
|
||||
download: {
|
||||
timeout: '60s',
|
||||
target_directory: '/tmp',
|
||||
},
|
||||
limits: { go_max_procs: 2 },
|
||||
logging: { metrics: { period: '10s' } },
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformOutputToFullPolicyOutput', () => {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import { safeLoad } from 'js-yaml';
|
||||
import deepMerge from 'deepmerge';
|
||||
import { set } from '@kbn/safer-lodash-set';
|
||||
|
||||
import {
|
||||
getDefaultPresetForEsOutput,
|
||||
|
@ -37,7 +38,7 @@ import {
|
|||
kafkaCompressionType,
|
||||
outputType,
|
||||
} from '../../../common/constants';
|
||||
|
||||
import { getSettingsValuesForAgentPolicy } from '../form_settings';
|
||||
import { getPackageInfo } from '../epm/packages';
|
||||
import { pkgToPkgKey, splitPkgKey } from '../epm/registry';
|
||||
import { appContextService } from '../app_context';
|
||||
|
@ -242,6 +243,14 @@ export async function getFullAgentPolicy(
|
|||
fullAgentPolicy.fleet = generateFleetConfig(fleetServerHosts, proxies);
|
||||
}
|
||||
|
||||
const settingsValues = getSettingsValuesForAgentPolicy(
|
||||
'AGENT_POLICY_ADVANCED_SETTINGS',
|
||||
agentPolicy
|
||||
);
|
||||
Object.entries(settingsValues).forEach(([settingsKey, settingValue]) => {
|
||||
set(fullAgentPolicy, settingsKey, settingValue);
|
||||
});
|
||||
|
||||
// populate protection and signed properties
|
||||
const messageSigningService = appContextService.getMessageSigningService();
|
||||
if (messageSigningService && fullAgentPolicy.agent) {
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { z } from 'zod';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import type { SettingsConfig } from '../../../common/settings/types';
|
||||
|
||||
import { _getSettingsAPISchema, _getSettingsValuesForAgentPolicy } from './form_settings';
|
||||
|
||||
const TEST_SETTINGS: SettingsConfig[] = [
|
||||
{
|
||||
name: 'test.foo',
|
||||
title: 'test',
|
||||
description: 'test',
|
||||
schema: z.boolean(),
|
||||
api_field: {
|
||||
name: 'test_foo',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'test.foo.default_value',
|
||||
title: 'test',
|
||||
description: 'test',
|
||||
schema: z.string().default('test'),
|
||||
api_field: {
|
||||
name: 'test_foo_default_value',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('form_settings', () => {
|
||||
describe('_getSettingsAPISchema', () => {
|
||||
it('generate a valid API schema for api_field', () => {
|
||||
const apiSchema = schema.object(_getSettingsAPISchema(TEST_SETTINGS));
|
||||
|
||||
expect(() =>
|
||||
apiSchema.validate({
|
||||
advanced_settings: {
|
||||
test_foo: 'not valid',
|
||||
},
|
||||
})
|
||||
).toThrowError(/Expected boolean, received string/);
|
||||
|
||||
expect(() =>
|
||||
apiSchema.validate({
|
||||
advanced_settings: {
|
||||
test_foo: true,
|
||||
},
|
||||
})
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('generate a valid API schema for api_field with default value', () => {
|
||||
const apiSchema = schema.object(_getSettingsAPISchema(TEST_SETTINGS));
|
||||
const res = apiSchema.validate({ advanced_settings: {} });
|
||||
expect(res).toEqual({ advanced_settings: { test_foo_default_value: 'test' } });
|
||||
});
|
||||
});
|
||||
|
||||
describe('_getSettingsValuesForAgentPolicy', () => {
|
||||
it('generate the proper values for agent policy (full agent policy)', () => {
|
||||
const res = _getSettingsValuesForAgentPolicy(TEST_SETTINGS, {
|
||||
advanced_settings: {
|
||||
test_foo_default_value: 'test',
|
||||
},
|
||||
} as any);
|
||||
expect(res).toEqual({ 'test.foo.default_value': 'test' });
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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 { type Props, schema } from '@kbn/config-schema';
|
||||
import { stringifyZodError } from '@kbn/zod-helpers';
|
||||
|
||||
import type { SettingsConfig, SettingsSection } from '../../../common/settings/types';
|
||||
import { AGENT_POLICY_ADVANCED_SETTINGS } from '../../../common/settings';
|
||||
import type { AgentPolicy } from '../../types';
|
||||
|
||||
export function getSettingsAPISchema(settingSection: SettingsSection) {
|
||||
const settings = getSettings(settingSection);
|
||||
|
||||
return _getSettingsAPISchema(settings);
|
||||
}
|
||||
|
||||
export function _getSettingsAPISchema(settings: SettingsConfig[]): Props {
|
||||
const validations: Props = {};
|
||||
settings.forEach((setting) => {
|
||||
if (!setting.api_field) {
|
||||
return;
|
||||
}
|
||||
const defaultValueRes = setting.schema.safeParse(undefined);
|
||||
const defaultValue = defaultValueRes.success ? defaultValueRes.data : undefined;
|
||||
if (defaultValue) {
|
||||
validations[setting.api_field.name] = schema.oneOf(
|
||||
[
|
||||
schema.any({
|
||||
validate: (val: any) => {
|
||||
const res = setting.schema.safeParse(val);
|
||||
if (!res.success) {
|
||||
return stringifyZodError(res.error);
|
||||
}
|
||||
},
|
||||
}),
|
||||
schema.literal(null),
|
||||
],
|
||||
{
|
||||
defaultValue,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
validations[setting.api_field.name] = schema.maybe(
|
||||
schema.nullable(
|
||||
schema.any({
|
||||
validate: (val: any) => {
|
||||
const res = setting.schema.safeParse(val);
|
||||
if (!res.success) {
|
||||
return stringifyZodError(res.error);
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const advancedSettingsValidations: Props = {
|
||||
advanced_settings: schema.maybe(
|
||||
schema.object({
|
||||
...validations,
|
||||
})
|
||||
),
|
||||
};
|
||||
return advancedSettingsValidations;
|
||||
}
|
||||
|
||||
export function getSettingsValuesForAgentPolicy(
|
||||
settingSection: SettingsSection,
|
||||
agentPolicy: AgentPolicy
|
||||
) {
|
||||
const settings = getSettings(settingSection);
|
||||
|
||||
return _getSettingsValuesForAgentPolicy(settings, agentPolicy);
|
||||
}
|
||||
|
||||
export function _getSettingsValuesForAgentPolicy(
|
||||
settings: SettingsConfig[],
|
||||
agentPolicy: AgentPolicy
|
||||
) {
|
||||
const settingsValues: { [k: string]: any } = {};
|
||||
settings.forEach((setting) => {
|
||||
if (!setting.api_field) {
|
||||
return;
|
||||
}
|
||||
|
||||
const val = agentPolicy.advanced_settings?.[setting.api_field.name];
|
||||
if (val) {
|
||||
settingsValues[setting.name] = val;
|
||||
}
|
||||
});
|
||||
return settingsValues;
|
||||
}
|
||||
|
||||
export function getSettings(settingSection: SettingsSection) {
|
||||
if (settingSection === 'AGENT_POLICY_ADVANCED_SETTINGS') {
|
||||
return AGENT_POLICY_ADVANCED_SETTINGS;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid settings section ${settingSection}`);
|
||||
}
|
12
x-pack/plugins/fleet/server/services/form_settings/index.ts
Normal file
12
x-pack/plugins/fleet/server/services/form_settings/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 {
|
||||
getSettings,
|
||||
getSettingsAPISchema,
|
||||
getSettingsValuesForAgentPolicy,
|
||||
} from './form_settings';
|
|
@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema';
|
|||
|
||||
import { agentPolicyStatuses, dataTypes } from '../../../common/constants';
|
||||
import { isValidNamespace } from '../../../common/services';
|
||||
import { getSettingsAPISchema } from '../../services/form_settings';
|
||||
|
||||
import { PackagePolicySchema } from './package_policy';
|
||||
|
||||
|
@ -81,6 +82,7 @@ export const AgentPolicyBaseSchema = {
|
|||
})
|
||||
)
|
||||
),
|
||||
...getSettingsAPISchema('AGENT_POLICY_ADVANCED_SETTINGS'),
|
||||
};
|
||||
|
||||
export const NewAgentPolicySchema = schema.object({
|
||||
|
|
|
@ -105,5 +105,6 @@
|
|||
"@kbn/core-http-server-mocks",
|
||||
"@kbn/code-editor",
|
||||
"@kbn/core-test-helpers-model-versions",
|
||||
"@kbn/zod-helpers",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue