[Ingest] Agent config settings UI (#64854)
* Remove duplicate donut chart, move used donut chart closer to usage, clean up import paths * Change delete agent config to only single delete and delete all its associated data sources * Initial pass at settings tab * Reuse existing create agent config form instead * Fix delete api * Prevent nav drawer from hiding bottom bar (save action area) content * Remove delete config functionality from list page * Prevent API from deleting config with agents enrolled * Fix namespace populating in form * Display confirmation modal to deploy to agents if agents are detected * Adjust confirm delete copy * Fix i18n checks * Fix type check * Fix it again * De-dupe confirm modal * Fix i18n * Update agent config info after saving * Adjust skip unassign from agent config option schema in delete datasource method
This commit is contained in:
parent
fc2f2446eb
commit
8a304623d4
|
@ -49,16 +49,16 @@ export interface UpdateAgentConfigResponse {
|
|||
success: boolean;
|
||||
}
|
||||
|
||||
export interface DeleteAgentConfigsRequest {
|
||||
export interface DeleteAgentConfigRequest {
|
||||
body: {
|
||||
agentConfigIds: string[];
|
||||
agentConfigId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type DeleteAgentConfigsResponse = Array<{
|
||||
export interface DeleteAgentConfigResponse {
|
||||
id: string;
|
||||
success: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface GetFullAgentConfigRequest {
|
||||
params: {
|
||||
|
|
|
@ -18,8 +18,8 @@ import {
|
|||
CreateAgentConfigResponse,
|
||||
UpdateAgentConfigRequest,
|
||||
UpdateAgentConfigResponse,
|
||||
DeleteAgentConfigsRequest,
|
||||
DeleteAgentConfigsResponse,
|
||||
DeleteAgentConfigRequest,
|
||||
DeleteAgentConfigResponse,
|
||||
} from '../../types';
|
||||
|
||||
export const useGetAgentConfigs = (query: HttpFetchQuery = {}) => {
|
||||
|
@ -75,8 +75,8 @@ export const sendUpdateAgentConfig = (
|
|||
});
|
||||
};
|
||||
|
||||
export const sendDeleteAgentConfigs = (body: DeleteAgentConfigsRequest['body']) => {
|
||||
return sendRequest<DeleteAgentConfigsResponse>({
|
||||
export const sendDeleteAgentConfig = (body: DeleteAgentConfigRequest['body']) => {
|
||||
return sendRequest<DeleteAgentConfigResponse>({
|
||||
path: agentConfigRouteService.getDeletePath(),
|
||||
method: 'post',
|
||||
body: JSON.stringify(body),
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
@import '@elastic/eui/src/components/header/variables';
|
||||
@import '@elastic/eui/src/components/nav_drawer/variables';
|
||||
|
||||
/**
|
||||
* 1. Hack EUI so the bottom bar doesn't obscure the nav drawer flyout.
|
||||
*/
|
||||
.ingestManager__bottomBar {
|
||||
z-index: 0; /* 1 */
|
||||
left: $euiNavDrawerWidthCollapsed;
|
||||
}
|
||||
|
||||
.ingestManager__bottomBar-isNavDrawerLocked {
|
||||
left: $euiNavDrawerWidthExpanded;
|
||||
}
|
|
@ -23,6 +23,7 @@ import { IngestManagerOverview, EPMApp, AgentConfigApp, FleetApp, DataStreamApp
|
|||
import { CoreContext, DepsContext, ConfigContext, setHttpClient, useConfig } from './hooks';
|
||||
import { PackageInstallProvider } from './sections/epm/hooks';
|
||||
import { sendSetup } from './hooks/use_request/setup';
|
||||
import './index.scss';
|
||||
|
||||
export interface ProtectedRouteProps extends RouteProps {
|
||||
isAllowed?: boolean;
|
||||
|
|
|
@ -5,117 +5,92 @@
|
|||
*/
|
||||
|
||||
import React, { Fragment, useRef, useState } from 'react';
|
||||
import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
|
||||
import { EuiConfirmModal, EuiOverlayMask, EuiCallOut } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { AGENT_SAVED_OBJECT_TYPE } from '../../../constants';
|
||||
import { sendDeleteAgentConfigs, useCore, sendRequest } from '../../../hooks';
|
||||
import { sendDeleteAgentConfig, useCore, useConfig, sendRequest } from '../../../hooks';
|
||||
|
||||
interface Props {
|
||||
children: (deleteAgentConfigs: deleteAgentConfigs) => React.ReactElement;
|
||||
children: (deleteAgentConfig: DeleteAgentConfig) => React.ReactElement;
|
||||
}
|
||||
|
||||
export type deleteAgentConfigs = (agentConfigs: string[], onSuccess?: OnSuccessCallback) => void;
|
||||
export type DeleteAgentConfig = (agentConfig: string, onSuccess?: OnSuccessCallback) => void;
|
||||
|
||||
type OnSuccessCallback = (agentConfigsUnenrolled: string[]) => void;
|
||||
type OnSuccessCallback = (agentConfigDeleted: string) => void;
|
||||
|
||||
export const AgentConfigDeleteProvider: React.FunctionComponent<Props> = ({ children }) => {
|
||||
const { notifications } = useCore();
|
||||
const [agentConfigs, setAgentConfigs] = useState<string[]>([]);
|
||||
const {
|
||||
fleet: { enabled: isFleetEnabled },
|
||||
} = useConfig();
|
||||
const [agentConfig, setAgentConfig] = useState<string>();
|
||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||
const [isLoadingAgentsCount, setIsLoadingAgentsCount] = useState<boolean>(false);
|
||||
const [agentsCount, setAgentsCount] = useState<number>(0);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const onSuccessCallback = useRef<OnSuccessCallback | null>(null);
|
||||
|
||||
const deleteAgentConfigsPrompt: deleteAgentConfigs = (
|
||||
agentConfigsToDelete,
|
||||
const deleteAgentConfigPrompt: DeleteAgentConfig = (
|
||||
agentConfigToDelete,
|
||||
onSuccess = () => undefined
|
||||
) => {
|
||||
if (
|
||||
agentConfigsToDelete === undefined ||
|
||||
(Array.isArray(agentConfigsToDelete) && agentConfigsToDelete.length === 0)
|
||||
) {
|
||||
throw new Error('No agent configs specified for deletion');
|
||||
if (!agentConfigToDelete) {
|
||||
throw new Error('No agent config specified for deletion');
|
||||
}
|
||||
setIsModalOpen(true);
|
||||
setAgentConfigs(agentConfigsToDelete);
|
||||
fetchAgentsCount(agentConfigsToDelete);
|
||||
setAgentConfig(agentConfigToDelete);
|
||||
fetchAgentsCount(agentConfigToDelete);
|
||||
onSuccessCallback.current = onSuccess;
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setAgentConfigs([]);
|
||||
setAgentConfig(undefined);
|
||||
setIsLoading(false);
|
||||
setIsLoadingAgentsCount(false);
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
||||
const deleteAgentConfigs = async () => {
|
||||
const deleteAgentConfig = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const { data } = await sendDeleteAgentConfigs({
|
||||
agentConfigIds: agentConfigs,
|
||||
const { data } = await sendDeleteAgentConfig({
|
||||
agentConfigId: agentConfig!,
|
||||
});
|
||||
const successfulResults = data?.filter(result => result.success) || [];
|
||||
const failedResults = data?.filter(result => !result.success) || [];
|
||||
|
||||
if (successfulResults.length) {
|
||||
const hasMultipleSuccesses = successfulResults.length > 1;
|
||||
const successMessage = hasMultipleSuccesses
|
||||
? i18n.translate(
|
||||
'xpack.ingestManager.deleteAgentConfigs.successMultipleNotificationTitle',
|
||||
{
|
||||
defaultMessage: 'Deleted {count} agent configs',
|
||||
values: { count: successfulResults.length },
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.ingestManager.deleteAgentConfigs.successSingleNotificationTitle',
|
||||
{
|
||||
defaultMessage: "Deleted agent config '{id}'",
|
||||
values: { id: successfulResults[0].id },
|
||||
}
|
||||
);
|
||||
notifications.toasts.addSuccess(successMessage);
|
||||
if (data?.success) {
|
||||
notifications.toasts.addSuccess(
|
||||
i18n.translate('xpack.ingestManager.deleteAgentConfig.successSingleNotificationTitle', {
|
||||
defaultMessage: "Deleted agent config '{id}'",
|
||||
values: { id: agentConfig },
|
||||
})
|
||||
);
|
||||
if (onSuccessCallback.current) {
|
||||
onSuccessCallback.current(agentConfig!);
|
||||
}
|
||||
}
|
||||
|
||||
if (failedResults.length) {
|
||||
const hasMultipleFailures = failedResults.length > 1;
|
||||
const failureMessage = hasMultipleFailures
|
||||
? i18n.translate(
|
||||
'xpack.ingestManager.deleteAgentConfigs.failureMultipleNotificationTitle',
|
||||
{
|
||||
defaultMessage: 'Error deleting {count} agent configs',
|
||||
values: { count: failedResults.length },
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.ingestManager.deleteAgentConfigs.failureSingleNotificationTitle',
|
||||
{
|
||||
defaultMessage: "Error deleting agent config '{id}'",
|
||||
values: { id: failedResults[0].id },
|
||||
}
|
||||
);
|
||||
notifications.toasts.addDanger(failureMessage);
|
||||
}
|
||||
|
||||
if (onSuccessCallback.current) {
|
||||
onSuccessCallback.current(successfulResults.map(result => result.id));
|
||||
if (!data?.success) {
|
||||
notifications.toasts.addDanger(
|
||||
i18n.translate('xpack.ingestManager.deleteAgentConfig.failureSingleNotificationTitle', {
|
||||
defaultMessage: "Error deleting agent config '{id}'",
|
||||
values: { id: agentConfig },
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
notifications.toasts.addDanger(
|
||||
i18n.translate('xpack.ingestManager.deleteAgentConfigs.fatalErrorNotificationTitle', {
|
||||
defaultMessage: 'Error deleting agent configs',
|
||||
i18n.translate('xpack.ingestManager.deleteAgentConfig.fatalErrorNotificationTitle', {
|
||||
defaultMessage: 'Error deleting agent config',
|
||||
})
|
||||
);
|
||||
}
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const fetchAgentsCount = async (agentConfigsToCheck: string[]) => {
|
||||
if (isLoadingAgentsCount) {
|
||||
const fetchAgentsCount = async (agentConfigToCheck: string) => {
|
||||
if (!isFleetEnabled || isLoadingAgentsCount) {
|
||||
return;
|
||||
}
|
||||
setIsLoadingAgentsCount(true);
|
||||
|
@ -123,7 +98,7 @@ export const AgentConfigDeleteProvider: React.FunctionComponent<Props> = ({ chil
|
|||
path: `/api/ingest_manager/fleet/agents`,
|
||||
method: 'get',
|
||||
query: {
|
||||
kuery: `${AGENT_SAVED_OBJECT_TYPE}.config_id : (${agentConfigsToCheck.join(' or ')})`,
|
||||
kuery: `${AGENT_SAVED_OBJECT_TYPE}.config_id : ${agentConfigToCheck}`,
|
||||
},
|
||||
});
|
||||
setAgentsCount(data?.total || 0);
|
||||
|
@ -140,68 +115,61 @@ export const AgentConfigDeleteProvider: React.FunctionComponent<Props> = ({ chil
|
|||
<EuiConfirmModal
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.deleteAgentConfigs.confirmModal.deleteMultipleTitle"
|
||||
defaultMessage="Delete {count, plural, one {this agent config} other {# agent configs}}?"
|
||||
values={{ count: agentConfigs.length }}
|
||||
id="xpack.ingestManager.deleteAgentConfig.confirmModal.deleteConfigTitle"
|
||||
defaultMessage="Delete this agent configuration?"
|
||||
/>
|
||||
}
|
||||
onCancel={closeModal}
|
||||
onConfirm={deleteAgentConfigs}
|
||||
onConfirm={deleteAgentConfig}
|
||||
cancelButtonText={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.deleteAgentConfigs.confirmModal.cancelButtonLabel"
|
||||
id="xpack.ingestManager.deleteAgentConfig.confirmModal.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
}
|
||||
confirmButtonText={
|
||||
isLoading || isLoadingAgentsCount ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.deleteAgentConfigs.confirmModal.loadingButtonLabel"
|
||||
id="xpack.ingestManager.deleteAgentConfig.confirmModal.loadingButtonLabel"
|
||||
defaultMessage="Loading…"
|
||||
/>
|
||||
) : agentsCount ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.deleteAgentConfigs.confirmModal.confirmAndReassignButtonLabel"
|
||||
defaultMessage="Delete {agentConfigsCount, plural, one {agent config} other {agent configs}} and unenroll {agentsCount, plural, one {agent} other {agents}}"
|
||||
values={{
|
||||
agentsCount,
|
||||
agentConfigsCount: agentConfigs.length,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.deleteAgentConfigs.confirmModal.confirmButtonLabel"
|
||||
defaultMessage="Delete {agentConfigsCount, plural, one {agent config} other {agent configs}}"
|
||||
values={{
|
||||
agentConfigsCount: agentConfigs.length,
|
||||
}}
|
||||
id="xpack.ingestManager.deleteAgentConfig.confirmModal.confirmButtonLabel"
|
||||
defaultMessage="Delete configuration"
|
||||
/>
|
||||
)
|
||||
}
|
||||
buttonColor="danger"
|
||||
confirmButtonDisabled={isLoading || isLoadingAgentsCount}
|
||||
confirmButtonDisabled={isLoading || isLoadingAgentsCount || !!agentsCount}
|
||||
>
|
||||
{isLoadingAgentsCount ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.deleteAgentConfigs.confirmModal.loadingAgentsCountMessage"
|
||||
id="xpack.ingestManager.deleteAgentConfig.confirmModal.loadingAgentsCountMessage"
|
||||
defaultMessage="Checking amount of affected agents…"
|
||||
/>
|
||||
) : agentsCount ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.deleteAgentConfigs.confirmModal.affectedAgentsMessage"
|
||||
defaultMessage="{agentsCount, plural, one {# agent is} other {# agents are}} assigned {agentConfigsCount, plural, one {to this agent config} other {across these agentConfigs}}. {agentsCount, plural, one {This agent} other {These agents}} will be unenrolled."
|
||||
values={{
|
||||
agentsCount,
|
||||
agentConfigsCount: agentConfigs.length,
|
||||
}}
|
||||
/>
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
title={i18n.translate(
|
||||
'xpack.ingestManager.deleteAgentConfig.confirmModal.affectedAgentsTitle',
|
||||
{
|
||||
defaultMessage: 'Configuration in use',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.deleteAgentConfig.confirmModal.affectedAgentsMessage"
|
||||
defaultMessage="{agentsCount, plural, one {# agent is} other {# agents are}} assigned to this agent configuration. Unassign these agents before deleting this configuration."
|
||||
values={{
|
||||
agentsCount,
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.deleteAgentConfigs.confirmModal.noAffectedAgentsMessage"
|
||||
defaultMessage="There are no agents assigned to {agentConfigsCount, plural, one {this agent config} other {these agentConfigs}}."
|
||||
values={{
|
||||
agentConfigsCount: agentConfigs.length,
|
||||
}}
|
||||
id="xpack.ingestManager.deleteAgentConfig.confirmModal.irreversibleMessage"
|
||||
defaultMessage="This action cannot be undone."
|
||||
/>
|
||||
)}
|
||||
</EuiConfirmModal>
|
||||
|
@ -211,7 +179,7 @@ export const AgentConfigDeleteProvider: React.FunctionComponent<Props> = ({ chil
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
{children(deleteAgentConfigsPrompt)}
|
||||
{children(deleteAgentConfigPrompt)}
|
||||
{renderModal()}
|
||||
</Fragment>
|
||||
);
|
||||
|
|
|
@ -8,8 +8,7 @@ import React, { useMemo, useState } from 'react';
|
|||
import {
|
||||
EuiAccordion,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiDescribedFormGroup,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiHorizontalRule,
|
||||
|
@ -19,11 +18,13 @@ import {
|
|||
EuiComboBox,
|
||||
EuiIconTip,
|
||||
EuiCheckboxGroup,
|
||||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import styled from 'styled-components';
|
||||
import { NewAgentConfig } from '../../../types';
|
||||
import { NewAgentConfig, AgentConfig } from '../../../types';
|
||||
import { AgentConfigDeleteProvider } from './config_delete_provider';
|
||||
|
||||
interface ValidationResults {
|
||||
[key: string]: JSX.Element[];
|
||||
|
@ -36,7 +37,7 @@ const StyledEuiAccordion = styled(EuiAccordion)`
|
|||
`;
|
||||
|
||||
export const agentConfigFormValidation = (
|
||||
agentConfig: Partial<NewAgentConfig>
|
||||
agentConfig: Partial<NewAgentConfig | AgentConfig>
|
||||
): ValidationResults => {
|
||||
const errors: ValidationResults = {};
|
||||
|
||||
|
@ -53,11 +54,13 @@ export const agentConfigFormValidation = (
|
|||
};
|
||||
|
||||
interface Props {
|
||||
agentConfig: Partial<NewAgentConfig>;
|
||||
updateAgentConfig: (u: Partial<NewAgentConfig>) => void;
|
||||
agentConfig: Partial<NewAgentConfig | AgentConfig>;
|
||||
updateAgentConfig: (u: Partial<NewAgentConfig | AgentConfig>) => void;
|
||||
withSysMonitoring: boolean;
|
||||
updateSysMonitoring: (newValue: boolean) => void;
|
||||
validation: ValidationResults;
|
||||
isEditing?: boolean;
|
||||
onDelete?: () => void;
|
||||
}
|
||||
|
||||
export const AgentConfigForm: React.FunctionComponent<Props> = ({
|
||||
|
@ -66,9 +69,11 @@ export const AgentConfigForm: React.FunctionComponent<Props> = ({
|
|||
withSysMonitoring,
|
||||
updateSysMonitoring,
|
||||
validation,
|
||||
isEditing = false,
|
||||
onDelete = () => {},
|
||||
}) => {
|
||||
const [touchedFields, setTouchedFields] = useState<{ [key: string]: boolean }>({});
|
||||
const [showNamespace, setShowNamespace] = useState<boolean>(false);
|
||||
const [showNamespace, setShowNamespace] = useState<boolean>(!!agentConfig.namespace);
|
||||
const fields: Array<{
|
||||
name: 'name' | 'description' | 'namespace';
|
||||
label: JSX.Element;
|
||||
|
@ -105,209 +110,281 @@ export const AgentConfigForm: React.FunctionComponent<Props> = ({
|
|||
];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EuiForm>
|
||||
{fields.map(({ name, label, placeholder }) => {
|
||||
return (
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
key={name}
|
||||
label={label}
|
||||
error={touchedFields[name] && validation[name] ? validation[name] : null}
|
||||
isInvalid={Boolean(touchedFields[name] && validation[name])}
|
||||
>
|
||||
<EuiFieldText
|
||||
fullWidth
|
||||
value={agentConfig[name]}
|
||||
onChange={e => updateAgentConfig({ [name]: e.target.value })}
|
||||
isInvalid={Boolean(touchedFields[name] && validation[name])}
|
||||
onBlur={() => setTouchedFields({ ...touchedFields, [name]: true })}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
})}
|
||||
const generalSettingsWrapper = (children: JSX.Element[]) => (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.configForm.generalSettingsGroupTitle"
|
||||
defaultMessage="General settings"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.configForm.generalSettingsGroupDescription"
|
||||
defaultMessage="Choose a name and description for your agent configuration."
|
||||
/>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
|
||||
const generalFields = fields.map(({ name, label, placeholder }) => {
|
||||
return (
|
||||
<EuiFormRow
|
||||
label={
|
||||
<EuiText size="xs" color="subdued">
|
||||
fullWidth
|
||||
key={name}
|
||||
label={label}
|
||||
error={touchedFields[name] && validation[name] ? validation[name] : null}
|
||||
isInvalid={Boolean(touchedFields[name] && validation[name])}
|
||||
>
|
||||
<EuiFieldText
|
||||
fullWidth
|
||||
value={agentConfig[name]}
|
||||
onChange={e => updateAgentConfig({ [name]: e.target.value })}
|
||||
isInvalid={Boolean(touchedFields[name] && validation[name])}
|
||||
onBlur={() => setTouchedFields({ ...touchedFields, [name]: true })}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
});
|
||||
|
||||
const advancedOptionsContent = (
|
||||
<>
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigForm.systemMonitoringFieldLabel"
|
||||
defaultMessage="Optional"
|
||||
id="xpack.ingestManager.agentConfigForm.namespaceFieldLabel"
|
||||
defaultMessage="Default namespace"
|
||||
/>
|
||||
</EuiText>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigForm.namespaceFieldDescription"
|
||||
defaultMessage="Apply a default namespace to data sources that use this configuration. Data sources can specify their own namespaces."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSwitch
|
||||
showLabel={true}
|
||||
label={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigForm.systemMonitoringText"
|
||||
defaultMessage="Collect system metrics"
|
||||
/>{' '}
|
||||
<EuiIconTip
|
||||
content={i18n.translate(
|
||||
'xpack.ingestManager.agentConfigForm.systemMonitoringTooltipText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Enable this option to bootstrap your configuration with a data source that collects system metrics and information.',
|
||||
}
|
||||
)}
|
||||
position="right"
|
||||
type="iInCircle"
|
||||
/>
|
||||
</>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigForm.namespaceUseDefaultsFieldLabel"
|
||||
defaultMessage="Use default namespace"
|
||||
/>
|
||||
}
|
||||
checked={withSysMonitoring}
|
||||
checked={showNamespace}
|
||||
onChange={() => {
|
||||
updateSysMonitoring(!withSysMonitoring);
|
||||
setShowNamespace(!showNamespace);
|
||||
if (showNamespace) {
|
||||
updateAgentConfig({ namespace: '' });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiHorizontalRule />
|
||||
<EuiSpacer size="xs" />
|
||||
<StyledEuiAccordion
|
||||
id="advancedOptions"
|
||||
buttonContent={
|
||||
{showNamespace && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
error={touchedFields.namespace && validation.namespace ? validation.namespace : null}
|
||||
isInvalid={Boolean(touchedFields.namespace && validation.namespace)}
|
||||
>
|
||||
<EuiComboBox
|
||||
fullWidth
|
||||
singleSelection
|
||||
noSuggestions
|
||||
selectedOptions={agentConfig.namespace ? [{ label: agentConfig.namespace }] : []}
|
||||
onCreateOption={(value: string) => {
|
||||
updateAgentConfig({ namespace: value });
|
||||
}}
|
||||
onChange={selectedOptions => {
|
||||
updateAgentConfig({
|
||||
namespace: (selectedOptions.length ? selectedOptions[0] : '') as string,
|
||||
});
|
||||
}}
|
||||
isInvalid={Boolean(touchedFields.namespace && validation.namespace)}
|
||||
onBlur={() => setTouchedFields({ ...touchedFields, namespace: true })}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
)}
|
||||
</EuiDescribedFormGroup>
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigForm.monitoringLabel"
|
||||
defaultMessage="Agent monitoring"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigForm.advancedOptionsToggleLabel"
|
||||
defaultMessage="Advanced options"
|
||||
id="xpack.ingestManager.agentConfigForm.monitoringDescription"
|
||||
defaultMessage="Collect data about your agents for debugging and tracking performance."
|
||||
/>
|
||||
}
|
||||
buttonClassName="ingest-active-button"
|
||||
>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigForm.namespaceFieldLabel"
|
||||
defaultMessage="Default namespace"
|
||||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s">
|
||||
<EuiCheckboxGroup
|
||||
options={[
|
||||
{
|
||||
id: 'logs',
|
||||
label: i18n.translate(
|
||||
'xpack.ingestManager.agentConfigForm.monitoringLogsFieldLabel',
|
||||
{ defaultMessage: 'Collect agent logs' }
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'metrics',
|
||||
label: i18n.translate(
|
||||
'xpack.ingestManager.agentConfigForm.monitoringMetricsFieldLabel',
|
||||
{ defaultMessage: 'Collect agent metrics' }
|
||||
),
|
||||
},
|
||||
]}
|
||||
idToSelectedMap={(agentConfig.monitoring_enabled || []).reduce(
|
||||
(acc: { logs: boolean; metrics: boolean }, key) => {
|
||||
acc[key] = true;
|
||||
return acc;
|
||||
},
|
||||
{ logs: false, metrics: false }
|
||||
)}
|
||||
onChange={id => {
|
||||
if (id !== 'logs' && id !== 'metrics') {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasLogs =
|
||||
agentConfig.monitoring_enabled && agentConfig.monitoring_enabled.indexOf(id) >= 0;
|
||||
|
||||
const previousValues = agentConfig.monitoring_enabled || [];
|
||||
updateAgentConfig({
|
||||
monitoring_enabled: hasLogs
|
||||
? previousValues.filter(type => type !== id)
|
||||
: [...previousValues, id],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiDescribedFormGroup>
|
||||
{isEditing && 'id' in agentConfig ? (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigForm.namespaceFieldDescription"
|
||||
defaultMessage="Apply a default namespace to data sources that use this configuration. Data sources can specify their own namespaces."
|
||||
id="xpack.ingestManager.configForm.deleteConfigGroupTitle"
|
||||
defaultMessage="Delete configuration"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.configForm.deleteConfigGroupDescription"
|
||||
defaultMessage="Existing data will not be deleted."
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<AgentConfigDeleteProvider>
|
||||
{deleteAgentConfigPrompt => {
|
||||
return (
|
||||
<EuiButton
|
||||
color="danger"
|
||||
disabled={Boolean(agentConfig.is_default)}
|
||||
onClick={() => deleteAgentConfigPrompt(agentConfig.id!, onDelete)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.configForm.deleteConfigActionText"
|
||||
defaultMessage="Delete configuration"
|
||||
/>
|
||||
</EuiButton>
|
||||
);
|
||||
}}
|
||||
</AgentConfigDeleteProvider>
|
||||
{agentConfig.is_default ? (
|
||||
<>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText color="subdued" size="xs">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.configForm.unableToDeleteDefaultConfigText"
|
||||
defaultMessage="Default configuration cannot be deleted"
|
||||
/>
|
||||
</EuiText>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiForm>
|
||||
{!isEditing ? generalFields : generalSettingsWrapper(generalFields)}
|
||||
{!isEditing ? (
|
||||
<EuiFormRow
|
||||
label={
|
||||
<EuiText size="xs" color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigForm.systemMonitoringFieldLabel"
|
||||
defaultMessage="Optional"
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiSwitch
|
||||
showLabel={true}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigForm.namespaceUseDefaultsFieldLabel"
|
||||
defaultMessage="Use default namespace"
|
||||
/>
|
||||
}
|
||||
checked={showNamespace}
|
||||
onChange={() => {
|
||||
setShowNamespace(!showNamespace);
|
||||
if (showNamespace) {
|
||||
updateAgentConfig({ namespace: '' });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{showNamespace && (
|
||||
}
|
||||
>
|
||||
<EuiSwitch
|
||||
showLabel={true}
|
||||
label={
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
error={
|
||||
touchedFields.namespace && validation.namespace ? validation.namespace : null
|
||||
}
|
||||
isInvalid={Boolean(touchedFields.namespace && validation.namespace)}
|
||||
>
|
||||
<EuiComboBox
|
||||
fullWidth
|
||||
singleSelection
|
||||
noSuggestions
|
||||
selectedOptions={
|
||||
agentConfig.namespace ? [{ label: agentConfig.namespace }] : []
|
||||
}
|
||||
onCreateOption={(value: string) => {
|
||||
updateAgentConfig({ namespace: value });
|
||||
}}
|
||||
onChange={selectedOptions => {
|
||||
updateAgentConfig({
|
||||
namespace: (selectedOptions.length ? selectedOptions[0] : '') as string,
|
||||
});
|
||||
}}
|
||||
isInvalid={Boolean(touchedFields.namespace && validation.namespace)}
|
||||
onBlur={() => setTouchedFields({ ...touchedFields, namespace: true })}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigForm.monitoringLabel"
|
||||
defaultMessage="Agent monitoring"
|
||||
id="xpack.ingestManager.agentConfigForm.systemMonitoringText"
|
||||
defaultMessage="Collect system metrics"
|
||||
/>{' '}
|
||||
<EuiIconTip
|
||||
content={i18n.translate(
|
||||
'xpack.ingestManager.agentConfigForm.systemMonitoringTooltipText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Enable this option to bootstrap your configuration with a data source that collects system metrics and information.',
|
||||
}
|
||||
)}
|
||||
position="right"
|
||||
type="iInCircle"
|
||||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s">
|
||||
</>
|
||||
}
|
||||
checked={withSysMonitoring}
|
||||
onChange={() => {
|
||||
updateSysMonitoring(!withSysMonitoring);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : null}
|
||||
{!isEditing ? (
|
||||
<>
|
||||
<EuiHorizontalRule />
|
||||
<EuiSpacer size="xs" />
|
||||
<StyledEuiAccordion
|
||||
id="advancedOptions"
|
||||
buttonContent={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigForm.monitoringDescription"
|
||||
defaultMessage="Collect data about your agents for debugging and tracking performance."
|
||||
id="xpack.ingestManager.agentConfigForm.advancedOptionsToggleLabel"
|
||||
defaultMessage="Advanced options"
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiCheckboxGroup
|
||||
options={[
|
||||
{
|
||||
id: 'logs',
|
||||
label: i18n.translate(
|
||||
'xpack.ingestManager.agentConfigForm.monitoringLogsFieldLabel',
|
||||
{ defaultMessage: 'Collect agent logs' }
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'metrics',
|
||||
label: i18n.translate(
|
||||
'xpack.ingestManager.agentConfigForm.monitoringMetricsFieldLabel',
|
||||
{ defaultMessage: 'Collect agent metrics' }
|
||||
),
|
||||
},
|
||||
]}
|
||||
idToSelectedMap={(agentConfig.monitoring_enabled || []).reduce(
|
||||
(acc: { logs: boolean; metrics: boolean }, key) => {
|
||||
acc[key] = true;
|
||||
return acc;
|
||||
},
|
||||
{ logs: false, metrics: false }
|
||||
)}
|
||||
onChange={id => {
|
||||
if (id !== 'logs' && id !== 'metrics') {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasLogs =
|
||||
agentConfig.monitoring_enabled && agentConfig.monitoring_enabled.indexOf(id) >= 0;
|
||||
|
||||
const previousValues = agentConfig.monitoring_enabled || [];
|
||||
updateAgentConfig({
|
||||
monitoring_enabled: hasLogs
|
||||
? previousValues.filter(type => type !== id)
|
||||
: [...previousValues, id],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</StyledEuiAccordion>
|
||||
}
|
||||
buttonClassName="ingest-active-button"
|
||||
>
|
||||
<EuiSpacer size="l" />
|
||||
{advancedOptionsContent}
|
||||
</StyledEuiAccordion>
|
||||
</>
|
||||
) : (
|
||||
advancedOptionsContent
|
||||
)}
|
||||
</EuiForm>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,9 +8,9 @@ import React from 'react';
|
|||
import { EuiCallOut, EuiOverlayMask, EuiConfirmModal, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AgentConfig } from '../../../../types';
|
||||
import { AgentConfig } from '../../../types';
|
||||
|
||||
export const ConfirmCreateDatasourceModal: React.FunctionComponent<{
|
||||
export const ConfirmDeployConfigModal: React.FunctionComponent<{
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
agentCount: number;
|
||||
|
@ -21,7 +21,7 @@ export const ConfirmCreateDatasourceModal: React.FunctionComponent<{
|
|||
<EuiConfirmModal
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createDatasource.confirmModalTitle"
|
||||
id="xpack.ingestManager.agentConfig.confirmModalTitle"
|
||||
defaultMessage="Save and deploy changes"
|
||||
/>
|
||||
}
|
||||
|
@ -29,13 +29,13 @@ export const ConfirmCreateDatasourceModal: React.FunctionComponent<{
|
|||
onConfirm={onConfirm}
|
||||
cancelButtonText={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.deleteApiKeys.confirmModal.cancelButtonLabel"
|
||||
id="xpack.ingestManager.agentConfig.confirmModalCancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
}
|
||||
confirmButtonText={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createDatasource.confirmModalConfirmButtonLabel"
|
||||
id="xpack.ingestManager.agentConfig.confirmModalConfirmButtonLabel"
|
||||
defaultMessage="Save and deploy changes"
|
||||
/>
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ export const ConfirmCreateDatasourceModal: React.FunctionComponent<{
|
|||
>
|
||||
<EuiCallOut
|
||||
iconType="iInCircle"
|
||||
title={i18n.translate('xpack.ingestManager.createDatasource.confirmModalCalloutTitle', {
|
||||
title={i18n.translate('xpack.ingestManager.agentConfig.confirmModalCalloutTitle', {
|
||||
defaultMessage:
|
||||
'This action will update {agentCount, plural, one {# agent} other {# agents}}',
|
||||
values: {
|
||||
|
@ -52,7 +52,7 @@ export const ConfirmCreateDatasourceModal: React.FunctionComponent<{
|
|||
})}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createDatasource.confirmModalCalloutDescription"
|
||||
id="xpack.ingestManager.agentConfig.confirmModalCalloutDescription"
|
||||
defaultMessage="Fleet has detected that the selected agent configuration, {configName}, is already in use by
|
||||
some of your agents. As a result of this action, Fleet will deploy updates to all agents
|
||||
that use this configuration."
|
||||
|
@ -63,7 +63,7 @@ export const ConfirmCreateDatasourceModal: React.FunctionComponent<{
|
|||
</EuiCallOut>
|
||||
<EuiSpacer size="l" />
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createDatasource.confirmModalDescription"
|
||||
id="xpack.ingestManager.agentConfig.confirmModalDescription"
|
||||
defaultMessage="This action can not be undone. Are you sure you wish to continue?"
|
||||
/>
|
||||
</EuiConfirmModal>
|
|
@ -7,3 +7,4 @@
|
|||
export { AgentConfigForm, agentConfigFormValidation } from './config_form';
|
||||
export { AgentConfigDeleteProvider } from './config_delete_provider';
|
||||
export { LinkedAgentCount } from './linked_agent_count';
|
||||
export { ConfirmDeployConfigModal } from './confirm_deploy_modal';
|
||||
|
|
|
@ -8,7 +8,7 @@ import React, { memo } from 'react';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import { useLink } from '../../../hooks';
|
||||
import { FLEET_AGENTS_PATH } from '../../../constants';
|
||||
import { FLEET_AGENTS_PATH, AGENT_SAVED_OBJECT_TYPE } from '../../../constants';
|
||||
|
||||
export const LinkedAgentCount = memo<{ count: number; agentConfigId: string }>(
|
||||
({ count, agentConfigId }) => {
|
||||
|
@ -21,7 +21,7 @@ export const LinkedAgentCount = memo<{ count: number; agentConfigId: string }>(
|
|||
/>
|
||||
);
|
||||
return count > 0 ? (
|
||||
<EuiLink href={`${FLEET_URI}?kuery=agents.config_id : ${agentConfigId}`}>
|
||||
<EuiLink href={`${FLEET_URI}?kuery=${AGENT_SAVED_OBJECT_TYPE}.config_id : ${agentConfigId}`}>
|
||||
{displayValue}
|
||||
</EuiLink>
|
||||
) : (
|
||||
|
|
|
@ -5,5 +5,4 @@
|
|||
*/
|
||||
export { CreateDatasourcePageLayout } from './layout';
|
||||
export { DatasourceInputPanel } from './datasource_input_panel';
|
||||
export { ConfirmCreateDatasourceModal } from './confirm_modal';
|
||||
export { DatasourceInputVarField } from './datasource_input_var_field';
|
||||
|
|
|
@ -27,7 +27,8 @@ import {
|
|||
sendGetAgentStatus,
|
||||
} from '../../../hooks';
|
||||
import { useLinks as useEPMLinks } from '../../epm/hooks';
|
||||
import { CreateDatasourcePageLayout, ConfirmCreateDatasourceModal } from './components';
|
||||
import { ConfirmDeployConfigModal } from '../components';
|
||||
import { CreateDatasourcePageLayout } from './components';
|
||||
import { CreateDatasourceFrom, DatasourceFormState } from './types';
|
||||
import { DatasourceValidationResults, validateDatasource, validationHasErrors } from './services';
|
||||
import { StepSelectPackage } from './step_select_package';
|
||||
|
@ -36,7 +37,10 @@ import { StepConfigureDatasource } from './step_configure_datasource';
|
|||
import { StepDefineDatasource } from './step_define_datasource';
|
||||
|
||||
export const CreateDatasourcePage: React.FunctionComponent = () => {
|
||||
const { notifications } = useCore();
|
||||
const {
|
||||
notifications,
|
||||
chrome: { getIsNavDrawerLocked$ },
|
||||
} = useCore();
|
||||
const {
|
||||
fleet: { enabled: isFleetEnabled },
|
||||
} = useConfig();
|
||||
|
@ -45,6 +49,15 @@ export const CreateDatasourcePage: React.FunctionComponent = () => {
|
|||
} = useRouteMatch();
|
||||
const history = useHistory();
|
||||
const from: CreateDatasourceFrom = configId ? 'config' : 'package';
|
||||
const [isNavDrawerLocked, setIsNavDrawerLocked] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = getIsNavDrawerLocked$().subscribe((newIsNavDrawerLocked: boolean) => {
|
||||
setIsNavDrawerLocked(newIsNavDrawerLocked);
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
});
|
||||
|
||||
// Agent config and package info states
|
||||
const [agentConfig, setAgentConfig] = useState<AgentConfig>();
|
||||
|
@ -269,7 +282,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => {
|
|||
return (
|
||||
<CreateDatasourcePageLayout {...layoutProps}>
|
||||
{formState === 'CONFIRM' && agentConfig && (
|
||||
<ConfirmCreateDatasourceModal
|
||||
<ConfirmDeployConfigModal
|
||||
agentCount={agentCount}
|
||||
agentConfig={agentConfig}
|
||||
onConfirm={onSubmit}
|
||||
|
@ -278,7 +291,14 @@ export const CreateDatasourcePage: React.FunctionComponent = () => {
|
|||
)}
|
||||
<EuiSteps steps={steps} />
|
||||
<EuiSpacer size="l" />
|
||||
<EuiBottomBar css={{ zIndex: 5 }} paddingSize="s">
|
||||
<EuiBottomBar
|
||||
css={{ zIndex: 5 }}
|
||||
className={
|
||||
isNavDrawerLocked
|
||||
? 'ingestManager__bottomBar-isNavDrawerLocked'
|
||||
: 'ingestManager__bottomBar'
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty color="ghost" href={cancelUrl}>
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { EuiFieldText, EuiForm, EuiFormRow } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { AgentConfig } from '../../../../types';
|
||||
|
||||
interface ValidationResults {
|
||||
[key: string]: JSX.Element[];
|
||||
}
|
||||
|
||||
export const configFormValidation = (config: Partial<AgentConfig>): ValidationResults => {
|
||||
const errors: ValidationResults = {};
|
||||
|
||||
if (!config.name?.trim()) {
|
||||
errors.name = [
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.configForm.nameRequiredErrorMessage"
|
||||
defaultMessage="Config name is required"
|
||||
/>,
|
||||
];
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
config: Partial<AgentConfig>;
|
||||
updateConfig: (u: Partial<AgentConfig>) => void;
|
||||
validation: ValidationResults;
|
||||
}
|
||||
|
||||
export const ConfigForm: React.FunctionComponent<Props> = ({
|
||||
config,
|
||||
updateConfig,
|
||||
validation,
|
||||
}) => {
|
||||
const [touchedFields, setTouchedFields] = useState<{ [key: string]: boolean }>({});
|
||||
const fields: Array<{ name: 'name' | 'description' | 'namespace'; label: JSX.Element }> = [
|
||||
{
|
||||
name: 'name',
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.configForm.nameFieldLabel"
|
||||
defaultMessage="Name"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.configForm.descriptionFieldLabel"
|
||||
defaultMessage="Description"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'namespace',
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.configForm.namespaceFieldLabel"
|
||||
defaultMessage="Namespace"
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiForm>
|
||||
{fields.map(({ name, label }) => {
|
||||
return (
|
||||
<EuiFormRow
|
||||
key={name}
|
||||
label={label}
|
||||
error={touchedFields[name] && validation[name] ? validation[name] : null}
|
||||
isInvalid={Boolean(touchedFields[name] && validation[name])}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={config[name]}
|
||||
onChange={e => updateConfig({ [name]: e.target.value })}
|
||||
isInvalid={Boolean(touchedFields[name] && validation[name])}
|
||||
onBlur={() => setTouchedFields({ ...touchedFields, [name]: true })}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
})}
|
||||
</EuiForm>
|
||||
);
|
||||
};
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* 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, useRef } from 'react';
|
||||
import d3 from 'd3';
|
||||
import { EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
interface DonutChartProps {
|
||||
data: {
|
||||
[key: string]: number;
|
||||
};
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export const DonutChart = ({ height, width, data }: DonutChartProps) => {
|
||||
const chartElement = useRef<SVGSVGElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (chartElement.current !== null) {
|
||||
// we must remove any existing paths before painting
|
||||
d3.selectAll('g').remove();
|
||||
const svgElement = d3
|
||||
.select(chartElement.current)
|
||||
.append('g')
|
||||
.attr('transform', `translate(${width / 2}, ${height / 2})`);
|
||||
const color = d3.scale
|
||||
.ordinal()
|
||||
// @ts-ignore
|
||||
.domain(data)
|
||||
.range(['#017D73', '#98A2B3', '#BD271E']);
|
||||
const pieGenerator = d3.layout
|
||||
.pie()
|
||||
.value(({ value }: any) => value)
|
||||
// these start/end angles will reverse the direction of the pie,
|
||||
// which matches our design
|
||||
.startAngle(2 * Math.PI)
|
||||
.endAngle(0);
|
||||
|
||||
svgElement
|
||||
.selectAll('g')
|
||||
// @ts-ignore
|
||||
.data(pieGenerator(d3.entries(data)))
|
||||
.enter()
|
||||
.append('path')
|
||||
.attr(
|
||||
'd',
|
||||
// @ts-ignore attr does not expect a param of type Arc<Arc> but it behaves as desired
|
||||
d3.svg
|
||||
.arc()
|
||||
.innerRadius(width * 0.28)
|
||||
.outerRadius(Math.min(width, height) / 2 - 10)
|
||||
)
|
||||
.attr('fill', (d: any) => color(d.data.key));
|
||||
}
|
||||
}, [data, height, width]);
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<svg ref={chartElement} width={width} height={height} />
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
|
@ -1,135 +0,0 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import {
|
||||
EuiFlyout,
|
||||
EuiFlyoutHeader,
|
||||
EuiTitle,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { useCore, sendRequest } from '../../../../hooks';
|
||||
import { agentConfigRouteService } from '../../../../services';
|
||||
import { AgentConfig } from '../../../../types';
|
||||
import { ConfigForm, configFormValidation } from './config_form';
|
||||
|
||||
interface Props {
|
||||
agentConfig: AgentConfig;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const EditConfigFlyout: React.FunctionComponent<Props> = ({
|
||||
agentConfig: originalAgentConfig,
|
||||
onClose,
|
||||
}) => {
|
||||
const { notifications } = useCore();
|
||||
const [config, setConfig] = useState<Partial<AgentConfig>>({
|
||||
name: originalAgentConfig.name,
|
||||
description: originalAgentConfig.description,
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const updateConfig = (updatedFields: Partial<AgentConfig>) => {
|
||||
setConfig({
|
||||
...config,
|
||||
...updatedFields,
|
||||
});
|
||||
};
|
||||
const validation = configFormValidation(config);
|
||||
|
||||
const header = (
|
||||
<EuiFlyoutHeader hasBorder aria-labelledby="FleetEditConfigFlyoutTitle">
|
||||
<EuiTitle size="m">
|
||||
<h2 id="FleetEditConfigFlyoutTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editConfig.flyoutTitle"
|
||||
defaultMessage="Edit config"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
);
|
||||
|
||||
const body = (
|
||||
<EuiFlyoutBody>
|
||||
<ConfigForm config={config} updateConfig={updateConfig} validation={validation} />
|
||||
</EuiFlyoutBody>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty iconType="cross" onClick={onClose} flush="left">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editConfig.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
isLoading={isLoading}
|
||||
disabled={isLoading || Object.keys(validation).length > 0}
|
||||
onClick={async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const { error } = await sendRequest({
|
||||
path: agentConfigRouteService.getUpdatePath(originalAgentConfig.id),
|
||||
method: 'put',
|
||||
body: JSON.stringify(config),
|
||||
});
|
||||
if (!error) {
|
||||
notifications.toasts.addSuccess(
|
||||
i18n.translate('xpack.ingestManager.editConfig.successNotificationTitle', {
|
||||
defaultMessage: "Agent config '{name}' updated",
|
||||
values: { name: config.name },
|
||||
})
|
||||
);
|
||||
} else {
|
||||
notifications.toasts.addDanger(
|
||||
error
|
||||
? error.message
|
||||
: i18n.translate('xpack.ingestManager.editConfig.errorNotificationTitle', {
|
||||
defaultMessage: 'Unable to update agent config',
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
notifications.toasts.addDanger(
|
||||
i18n.translate('xpack.ingestManager.editConfig.errorNotificationTitle', {
|
||||
defaultMessage: 'Unable to update agent config',
|
||||
})
|
||||
);
|
||||
}
|
||||
setIsLoading(false);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editConfig.submitButtonLabel"
|
||||
defaultMessage="Update"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={onClose} size="m" maxWidth={400}>
|
||||
{header}
|
||||
{body}
|
||||
{footer}
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
|
@ -4,5 +4,3 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
export { DatasourcesTable } from './datasources/datasources_table';
|
||||
export { DonutChart } from './donut_chart';
|
||||
export { EditConfigFlyout } from './edit_config';
|
||||
|
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { memo, useState, useEffect } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { EuiBottomBar, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiButton } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { AGENT_CONFIG_PATH } from '../../../../../constants';
|
||||
import { AgentConfig } from '../../../../../types';
|
||||
import {
|
||||
useCore,
|
||||
useCapabilities,
|
||||
sendUpdateAgentConfig,
|
||||
useConfig,
|
||||
sendGetAgentStatus,
|
||||
} from '../../../../../hooks';
|
||||
import {
|
||||
AgentConfigForm,
|
||||
agentConfigFormValidation,
|
||||
ConfirmDeployConfigModal,
|
||||
} from '../../../components';
|
||||
import { useConfigRefresh } from '../../hooks';
|
||||
|
||||
const FormWrapper = styled.div`
|
||||
max-width: 800px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
export const ConfigSettingsView = memo<{ config: AgentConfig }>(
|
||||
({ config: originalAgentConfig }) => {
|
||||
const {
|
||||
notifications,
|
||||
chrome: { getIsNavDrawerLocked$ },
|
||||
} = useCore();
|
||||
const {
|
||||
fleet: { enabled: isFleetEnabled },
|
||||
} = useConfig();
|
||||
const history = useHistory();
|
||||
const hasWriteCapabilites = useCapabilities().write;
|
||||
const refreshConfig = useConfigRefresh();
|
||||
const [isNavDrawerLocked, setIsNavDrawerLocked] = useState(false);
|
||||
const [agentConfig, setAgentConfig] = useState<AgentConfig>({
|
||||
...originalAgentConfig,
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [hasChanges, setHasChanges] = useState<boolean>(false);
|
||||
const [agentCount, setAgentCount] = useState<number>(0);
|
||||
const [withSysMonitoring, setWithSysMonitoring] = useState<boolean>(true);
|
||||
const validation = agentConfigFormValidation(agentConfig);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = getIsNavDrawerLocked$().subscribe((newIsNavDrawerLocked: boolean) => {
|
||||
setIsNavDrawerLocked(newIsNavDrawerLocked);
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
});
|
||||
|
||||
const updateAgentConfig = (updatedFields: Partial<AgentConfig>) => {
|
||||
setAgentConfig({
|
||||
...agentConfig,
|
||||
...updatedFields,
|
||||
});
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
const submitUpdateAgentConfig = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const { name, description, namespace, monitoring_enabled } = agentConfig;
|
||||
const { data, error } = await sendUpdateAgentConfig(agentConfig.id, {
|
||||
name,
|
||||
description,
|
||||
namespace,
|
||||
monitoring_enabled,
|
||||
});
|
||||
if (data?.success) {
|
||||
notifications.toasts.addSuccess(
|
||||
i18n.translate('xpack.ingestManager.editAgentConfig.successNotificationTitle', {
|
||||
defaultMessage: "Successfully updated '{name}' settings",
|
||||
values: { name: agentConfig.name },
|
||||
})
|
||||
);
|
||||
refreshConfig();
|
||||
setHasChanges(false);
|
||||
} else {
|
||||
notifications.toasts.addDanger(
|
||||
error
|
||||
? error.message
|
||||
: i18n.translate('xpack.ingestManager.editAgentConfig.errorNotificationTitle', {
|
||||
defaultMessage: 'Unable to update agent config',
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
notifications.toasts.addDanger(
|
||||
i18n.translate('xpack.ingestManager.editAgentConfig.errorNotificationTitle', {
|
||||
defaultMessage: 'Unable to update agent config',
|
||||
})
|
||||
);
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
// Retrieve agent count if fleet is enabled
|
||||
if (isFleetEnabled) {
|
||||
setIsLoading(true);
|
||||
const { data } = await sendGetAgentStatus({ configId: agentConfig.id });
|
||||
if (data?.results.total) {
|
||||
setAgentCount(data.results.total);
|
||||
} else {
|
||||
await submitUpdateAgentConfig();
|
||||
}
|
||||
} else {
|
||||
await submitUpdateAgentConfig();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormWrapper>
|
||||
{agentCount ? (
|
||||
<ConfirmDeployConfigModal
|
||||
agentCount={agentCount}
|
||||
agentConfig={agentConfig}
|
||||
onConfirm={() => {
|
||||
setAgentCount(0);
|
||||
submitUpdateAgentConfig();
|
||||
}}
|
||||
onCancel={() => {
|
||||
setAgentCount(0);
|
||||
setIsLoading(false);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<AgentConfigForm
|
||||
agentConfig={agentConfig}
|
||||
updateAgentConfig={updateAgentConfig}
|
||||
withSysMonitoring={withSysMonitoring}
|
||||
updateSysMonitoring={newValue => setWithSysMonitoring(newValue)}
|
||||
validation={validation}
|
||||
isEditing={true}
|
||||
onDelete={() => {
|
||||
history.push(AGENT_CONFIG_PATH);
|
||||
}}
|
||||
/>
|
||||
{hasChanges ? (
|
||||
<EuiBottomBar
|
||||
css={{ zIndex: 5 }}
|
||||
className={
|
||||
isNavDrawerLocked
|
||||
? 'ingestManager__bottomBar-isNavDrawerLocked'
|
||||
: 'ingestManager__bottomBar'
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editAgentConfig.unsavedChangesText"
|
||||
defaultMessage="You have unsaved changes"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
color="ghost"
|
||||
onClick={() => {
|
||||
setAgentConfig({ ...originalAgentConfig });
|
||||
setHasChanges(false);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editAgentConfig.cancelButtonText"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={onSubmit}
|
||||
isLoading={isLoading}
|
||||
isDisabled={
|
||||
!hasWriteCapabilites || isLoading || Object.keys(validation).length > 0
|
||||
}
|
||||
iconType="save"
|
||||
color="primary"
|
||||
fill
|
||||
>
|
||||
{isLoading ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editAgentConfig.savingButtonText"
|
||||
defaultMessage="Saving…"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editAgentConfig.saveButtonText"
|
||||
defaultMessage="Save changes"
|
||||
/>
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiBottomBar>
|
||||
) : null}
|
||||
</FormWrapper>
|
||||
);
|
||||
}
|
||||
);
|
|
@ -15,7 +15,7 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { AgentConfig } from '../../../../../../../../common/types/models';
|
||||
import { AgentConfig } from '../../../../../types';
|
||||
import {
|
||||
useGetOneAgentConfigFull,
|
||||
useGetEnrollmentAPIKeys,
|
||||
|
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
export { useGetAgentStatus, AgentStatusRefreshContext } from './use_agent_status';
|
||||
export { ConfigRefreshContext } from './use_config';
|
||||
export { ConfigRefreshContext, useConfigRefresh } from './use_config';
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* 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, { Fragment, memo, useCallback, useMemo, useState } from 'react';
|
||||
import React, { Fragment, memo, useMemo, useState } from 'react';
|
||||
import { Redirect, useRouteMatch, Switch, Route } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage, FormattedDate } from '@kbn/i18n/react';
|
||||
|
@ -26,12 +26,12 @@ import { useGetOneAgentConfig } from '../../../hooks';
|
|||
import { Loading } from '../../../components';
|
||||
import { WithHeaderLayout } from '../../../layouts';
|
||||
import { ConfigRefreshContext, useGetAgentStatus, AgentStatusRefreshContext } from './hooks';
|
||||
import { EditConfigFlyout } from './components';
|
||||
import { LinkedAgentCount } from '../components';
|
||||
import { useAgentConfigLink } from './hooks/use_details_uri';
|
||||
import { DETAILS_ROUTER_PATH, DETAILS_ROUTER_SUB_PATH } from './constants';
|
||||
import { ConfigDatasourcesView } from './components/datasources';
|
||||
import { ConfigYamlView } from './components/yaml';
|
||||
import { ConfigSettingsView } from './components/settings';
|
||||
|
||||
const Divider = styled.div`
|
||||
width: 0;
|
||||
|
@ -70,14 +70,6 @@ export const AgentConfigDetailsLayout: React.FunctionComponent = () => {
|
|||
const configDetailsYamlLink = useAgentConfigLink('details-yaml', { configId });
|
||||
const configDetailsSettingsLink = useAgentConfigLink('details-settings', { configId });
|
||||
|
||||
// Flyout states
|
||||
const [isEditConfigFlyoutOpen, setIsEditConfigFlyoutOpen] = useState<boolean>(false);
|
||||
|
||||
const refreshData = useCallback(() => {
|
||||
refreshAgentConfig();
|
||||
refreshAgentStatus();
|
||||
}, [refreshAgentConfig, refreshAgentStatus]);
|
||||
|
||||
const headerLeftContent = useMemo(
|
||||
() => (
|
||||
<React.Fragment>
|
||||
|
@ -196,7 +188,7 @@ export const AgentConfigDetailsLayout: React.FunctionComponent = () => {
|
|||
return [
|
||||
{
|
||||
id: 'datasources',
|
||||
name: i18n.translate('xpack.ingestManager.configDetails.subTabs.datasouces', {
|
||||
name: i18n.translate('xpack.ingestManager.configDetails.subTabs.datasourcesTabText', {
|
||||
defaultMessage: 'Data sources',
|
||||
}),
|
||||
href: configDetailsLink,
|
||||
|
@ -204,15 +196,15 @@ export const AgentConfigDetailsLayout: React.FunctionComponent = () => {
|
|||
},
|
||||
{
|
||||
id: 'yaml',
|
||||
name: i18n.translate('xpack.ingestManager.configDetails.subTabs.yamlFile', {
|
||||
defaultMessage: 'YAML File',
|
||||
name: i18n.translate('xpack.ingestManager.configDetails.subTabs.yamlTabText', {
|
||||
defaultMessage: 'YAML',
|
||||
}),
|
||||
href: configDetailsYamlLink,
|
||||
isSelected: tabId === 'yaml',
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
name: i18n.translate('xpack.ingestManager.configDetails.subTabs.settings', {
|
||||
name: i18n.translate('xpack.ingestManager.configDetails.subTabs.settingsTabText', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
href: configDetailsSettingsLink,
|
||||
|
@ -269,16 +261,6 @@ export const AgentConfigDetailsLayout: React.FunctionComponent = () => {
|
|||
rightColumn={headerRightContent}
|
||||
tabs={(headerTabs as unknown) as EuiTabProps[]}
|
||||
>
|
||||
{isEditConfigFlyoutOpen ? (
|
||||
<EditConfigFlyout
|
||||
onClose={() => {
|
||||
setIsEditConfigFlyoutOpen(false);
|
||||
refreshData();
|
||||
}}
|
||||
agentConfig={agentConfig}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Switch>
|
||||
<Route
|
||||
path={`${DETAILS_ROUTER_PATH}/yaml`}
|
||||
|
@ -289,8 +271,7 @@ export const AgentConfigDetailsLayout: React.FunctionComponent = () => {
|
|||
<Route
|
||||
path={`${DETAILS_ROUTER_PATH}/settings`}
|
||||
render={() => {
|
||||
// TODO: Settings implementation tracked via: https://github.com/elastic/kibana/issues/57959
|
||||
return <div>Settings placeholder</div>;
|
||||
return <ConfigSettingsView config={agentConfig} />;
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
|
|
|
@ -29,10 +29,8 @@ import {
|
|||
sendGetPackageInfoByKey,
|
||||
} from '../../../hooks';
|
||||
import { Loading, Error } from '../../../components';
|
||||
import {
|
||||
CreateDatasourcePageLayout,
|
||||
ConfirmCreateDatasourceModal,
|
||||
} from '../create_datasource_page/components';
|
||||
import { ConfirmDeployConfigModal } from '../components';
|
||||
import { CreateDatasourcePageLayout } from '../create_datasource_page/components';
|
||||
import {
|
||||
DatasourceValidationResults,
|
||||
validateDatasource,
|
||||
|
@ -243,7 +241,7 @@ export const EditDatasourcePage: React.FunctionComponent = () => {
|
|||
) : (
|
||||
<>
|
||||
{formState === 'CONFIRM' && (
|
||||
<ConfirmCreateDatasourceModal
|
||||
<ConfirmDeployConfigModal
|
||||
agentCount={agentCount}
|
||||
agentConfig={agentConfig}
|
||||
onConfirm={onSubmit}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
.fleet__agentList__table .euiTableFooterCell {
|
||||
.euiTableCellContent,
|
||||
.euiTableCellContent__text {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
|
@ -19,12 +19,12 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab';
|
||||
import { useRouteMatch } from 'react-router-dom';
|
||||
import { DonutChart } from '../agent_list_page/components/donut_chart';
|
||||
import { useGetAgentStatus } from '../../agent_config/details_page/hooks';
|
||||
import { useCapabilities, useLink, useGetAgentConfigs } from '../../../hooks';
|
||||
import { WithHeaderLayout } from '../../../layouts';
|
||||
import { FLEET_ENROLLMENT_TOKENS_PATH, FLEET_AGENTS_PATH } from '../../../constants';
|
||||
import { useCapabilities, useLink, useGetAgentConfigs } from '../../../hooks';
|
||||
import { useGetAgentStatus } from '../../agent_config/details_page/hooks';
|
||||
import { AgentEnrollmentFlyout } from '../agent_list_page/components';
|
||||
import { DonutChart } from './donut_chart';
|
||||
|
||||
const REFRESH_INTERVAL_MS = 5000;
|
||||
|
||||
|
|
|
@ -28,8 +28,8 @@ export {
|
|||
CreateAgentConfigResponse,
|
||||
UpdateAgentConfigRequest,
|
||||
UpdateAgentConfigResponse,
|
||||
DeleteAgentConfigsRequest,
|
||||
DeleteAgentConfigsResponse,
|
||||
DeleteAgentConfigRequest,
|
||||
DeleteAgentConfigResponse,
|
||||
// API schemas - Datasource
|
||||
CreateDatasourceRequest,
|
||||
CreateDatasourceResponse,
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
GetOneAgentConfigRequestSchema,
|
||||
CreateAgentConfigRequestSchema,
|
||||
UpdateAgentConfigRequestSchema,
|
||||
DeleteAgentConfigsRequestSchema,
|
||||
DeleteAgentConfigRequestSchema,
|
||||
GetFullAgentConfigRequestSchema,
|
||||
AgentConfig,
|
||||
DefaultPackages,
|
||||
|
@ -26,7 +26,7 @@ import {
|
|||
GetOneAgentConfigResponse,
|
||||
CreateAgentConfigResponse,
|
||||
UpdateAgentConfigResponse,
|
||||
DeleteAgentConfigsResponse,
|
||||
DeleteAgentConfigResponse,
|
||||
GetFullAgentConfigResponse,
|
||||
} from '../../../common';
|
||||
|
||||
|
@ -49,7 +49,7 @@ export const getAgentConfigsHandler: RequestHandler<
|
|||
items,
|
||||
(agentConfig: GetAgentConfigsResponseItem) =>
|
||||
listAgents(soClient, {
|
||||
showInactive: true,
|
||||
showInactive: false,
|
||||
perPage: 0,
|
||||
page: 1,
|
||||
kuery: `${AGENT_SAVED_OBJECT_TYPE}.config_id:${agentConfig.id}`,
|
||||
|
@ -179,13 +179,13 @@ export const updateAgentConfigHandler: RequestHandler<
|
|||
export const deleteAgentConfigsHandler: RequestHandler<
|
||||
unknown,
|
||||
unknown,
|
||||
TypeOf<typeof DeleteAgentConfigsRequestSchema.body>
|
||||
TypeOf<typeof DeleteAgentConfigRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
try {
|
||||
const body: DeleteAgentConfigsResponse = await agentConfigService.delete(
|
||||
const body: DeleteAgentConfigResponse = await agentConfigService.delete(
|
||||
soClient,
|
||||
request.body.agentConfigIds
|
||||
request.body.agentConfigId
|
||||
);
|
||||
return response.ok({
|
||||
body,
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
GetOneAgentConfigRequestSchema,
|
||||
CreateAgentConfigRequestSchema,
|
||||
UpdateAgentConfigRequestSchema,
|
||||
DeleteAgentConfigsRequestSchema,
|
||||
DeleteAgentConfigRequestSchema,
|
||||
GetFullAgentConfigRequestSchema,
|
||||
} from '../../types';
|
||||
import {
|
||||
|
@ -67,7 +67,7 @@ export const registerRoutes = (router: IRouter) => {
|
|||
router.post(
|
||||
{
|
||||
path: AGENT_CONFIG_API_ROUTES.DELETE_PATTERN,
|
||||
validate: DeleteAgentConfigsRequestSchema,
|
||||
validate: DeleteAgentConfigRequestSchema,
|
||||
options: { tags: [`access:${PLUGIN_ID}-all`] },
|
||||
},
|
||||
deleteAgentConfigsHandler
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
import { uniq } from 'lodash';
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
import { AuthenticatedUser } from '../../../security/server';
|
||||
import { DEFAULT_AGENT_CONFIG, AGENT_CONFIG_SAVED_OBJECT_TYPE } from '../constants';
|
||||
import {
|
||||
DEFAULT_AGENT_CONFIG,
|
||||
AGENT_CONFIG_SAVED_OBJECT_TYPE,
|
||||
AGENT_SAVED_OBJECT_TYPE,
|
||||
} from '../constants';
|
||||
import {
|
||||
Datasource,
|
||||
NewAgentConfig,
|
||||
|
@ -15,7 +19,8 @@ import {
|
|||
AgentConfigStatus,
|
||||
ListWithKuery,
|
||||
} from '../types';
|
||||
import { DeleteAgentConfigsResponse, storedDatasourceToAgentDatasource } from '../../common';
|
||||
import { DeleteAgentConfigResponse, storedDatasourceToAgentDatasource } from '../../common';
|
||||
import { listAgents } from './agents';
|
||||
import { datasourceService } from './datasource';
|
||||
import { outputService } from './output';
|
||||
import { agentConfigUpdateEventHandler } from './agent_config_update';
|
||||
|
@ -256,32 +261,40 @@ class AgentConfigService {
|
|||
|
||||
public async delete(
|
||||
soClient: SavedObjectsClientContract,
|
||||
ids: string[]
|
||||
): Promise<DeleteAgentConfigsResponse> {
|
||||
const result: DeleteAgentConfigsResponse = [];
|
||||
const defaultConfigId = await this.getDefaultAgentConfigId(soClient);
|
||||
id: string
|
||||
): Promise<DeleteAgentConfigResponse> {
|
||||
const config = await this.get(soClient, id, false);
|
||||
if (!config) {
|
||||
throw new Error('Agent configuration not found');
|
||||
}
|
||||
|
||||
if (ids.includes(defaultConfigId)) {
|
||||
const defaultConfigId = await this.getDefaultAgentConfigId(soClient);
|
||||
if (id === defaultConfigId) {
|
||||
throw new Error('The default agent configuration cannot be deleted');
|
||||
}
|
||||
|
||||
for (const id of ids) {
|
||||
try {
|
||||
await soClient.delete(SAVED_OBJECT_TYPE, id);
|
||||
await this.triggerAgentConfigUpdatedEvent(soClient, 'deleted', id);
|
||||
result.push({
|
||||
id,
|
||||
success: true,
|
||||
});
|
||||
} catch (e) {
|
||||
result.push({
|
||||
id,
|
||||
success: false,
|
||||
});
|
||||
}
|
||||
const { total } = await listAgents(soClient, {
|
||||
showInactive: false,
|
||||
perPage: 0,
|
||||
page: 1,
|
||||
kuery: `${AGENT_SAVED_OBJECT_TYPE}.config_id:${id}`,
|
||||
});
|
||||
|
||||
if (total > 0) {
|
||||
throw new Error('Cannot delete agent config that is assigned to agent(s)');
|
||||
}
|
||||
|
||||
return result;
|
||||
if (config.datasources && config.datasources.length) {
|
||||
await datasourceService.delete(soClient, config.datasources as string[], {
|
||||
skipUnassignFromAgentConfigs: true,
|
||||
});
|
||||
}
|
||||
await soClient.delete(SAVED_OBJECT_TYPE, id);
|
||||
await this.triggerAgentConfigUpdatedEvent(soClient, 'deleted', id);
|
||||
return {
|
||||
id,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
public async getFullConfig(
|
||||
|
|
|
@ -145,7 +145,7 @@ class DatasourceService {
|
|||
public async delete(
|
||||
soClient: SavedObjectsClientContract,
|
||||
ids: string[],
|
||||
options?: { user?: AuthenticatedUser }
|
||||
options?: { user?: AuthenticatedUser; skipUnassignFromAgentConfigs?: boolean }
|
||||
): Promise<DeleteDatasourcesResponse> {
|
||||
const result: DeleteDatasourcesResponse = [];
|
||||
|
||||
|
@ -155,14 +155,16 @@ class DatasourceService {
|
|||
if (!oldDatasource) {
|
||||
throw new Error('Datasource not found');
|
||||
}
|
||||
await agentConfigService.unassignDatasources(
|
||||
soClient,
|
||||
oldDatasource.config_id,
|
||||
[oldDatasource.id],
|
||||
{
|
||||
user: options?.user,
|
||||
}
|
||||
);
|
||||
if (!options?.skipUnassignFromAgentConfigs) {
|
||||
await agentConfigService.unassignDatasources(
|
||||
soClient,
|
||||
oldDatasource.config_id,
|
||||
[oldDatasource.id],
|
||||
{
|
||||
user: options?.user,
|
||||
}
|
||||
);
|
||||
}
|
||||
await soClient.delete(SAVED_OBJECT_TYPE, id);
|
||||
result.push({
|
||||
id,
|
||||
|
|
|
@ -29,9 +29,9 @@ export const UpdateAgentConfigRequestSchema = {
|
|||
body: NewAgentConfigSchema,
|
||||
};
|
||||
|
||||
export const DeleteAgentConfigsRequestSchema = {
|
||||
export const DeleteAgentConfigRequestSchema = {
|
||||
body: schema.object({
|
||||
agentConfigIds: schema.arrayOf(schema.string()),
|
||||
agentConfigId: schema.string(),
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
@ -8317,9 +8317,6 @@
|
|||
"xpack.ingestManager.configDetails.datasourcesTable.namespaceColumnTitle": "名前空間",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.packageNameColumnTitle": "パッケージ",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.streamsCountColumnTitle": "ストリーム",
|
||||
"xpack.ingestManager.configDetails.subTabs.datasouces": "データソース",
|
||||
"xpack.ingestManager.configDetails.subTabs.settings": "設定",
|
||||
"xpack.ingestManager.configDetails.subTabs.yamlFile": "YAML ファイル",
|
||||
"xpack.ingestManager.configDetails.summary.datasources": "データソース",
|
||||
"xpack.ingestManager.configDetails.summary.lastUpdated": "最終更新日:",
|
||||
"xpack.ingestManager.configDetails.summary.revision": "リビジョン",
|
||||
|
@ -8329,10 +8326,6 @@
|
|||
"xpack.ingestManager.configDetailsDatasources.createFirstButtonText": "データソースを作成",
|
||||
"xpack.ingestManager.configDetailsDatasources.createFirstMessage": "この構成にはデータソースはまだありません。",
|
||||
"xpack.ingestManager.configDetailsDatasources.createFirstTitle": "初めてのデーソースを作成する",
|
||||
"xpack.ingestManager.configForm.descriptionFieldLabel": "説明",
|
||||
"xpack.ingestManager.configForm.nameFieldLabel": "名前",
|
||||
"xpack.ingestManager.configForm.nameRequiredErrorMessage": "構成名が必要です",
|
||||
"xpack.ingestManager.configForm.namespaceFieldLabel": "名前空間",
|
||||
"xpack.ingestManager.createAgentConfig.cancelButtonLabel": "キャンセル",
|
||||
"xpack.ingestManager.createAgentConfig.errorNotificationTitle": "エージェント構成を作成できません",
|
||||
"xpack.ingestManager.createAgentConfig.flyoutTitle": "エージェント構成を作成",
|
||||
|
@ -8364,20 +8357,6 @@
|
|||
"xpack.ingestManager.createDatasource.stepSelectPackage.errorLoadingPackagesTitle": "パッケージの読み込みエラー",
|
||||
"xpack.ingestManager.createDatasource.stepSelectPackage.errorLoadingSelectedPackageTitle": "選択したパッケージの読み込みエラー",
|
||||
"xpack.ingestManager.createDatasource.stepSelectPackage.filterPackagesInputPlaceholder": "パッケージの検索",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.affectedAgentsMessage": "{agentsCount, plural, one {# エージェントを} other {# エージェントを}}{agentConfigsCount, plural, one {このエージェント構成に} other {これらのエージェント構成に}}割り当てました。 {agentsCount, plural, one {このエージェント} other {これらのエージェント}}の登録が解除されます。",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.cancelButtonLabel": "キャンセル",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.confirmAndReassignButtonLabel": "{agentConfigsCount, plural, one {エージェント構成} other {エージェント構成}} and unenroll {agentsCount, plural, one {エージェント} other {エージェント}} を削除",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.confirmButtonLabel": "{agentConfigsCount, plural, one {エージェント構成} other {エージェント構成}}を削除",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.deleteMultipleTitle": "{count, plural, one {this agent config} other {# agent configs}} を削除しますか?",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.loadingAgentsCountMessage": "影響があるエージェントの数を確認中...",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.loadingButtonLabel": "読み込み中...",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.noAffectedAgentsMessage": "{agentConfigsCount, plural, one {this agent config} other {these agentConfigs}}に割り当てられたエージェントはありません。",
|
||||
"xpack.ingestManager.deleteAgentConfigs.failureMultipleNotificationTitle": "{count} 件のエージェント構成の削除エラー",
|
||||
"xpack.ingestManager.deleteAgentConfigs.failureSingleNotificationTitle": "エージェント構成「{id}」の削除エラー",
|
||||
"xpack.ingestManager.deleteAgentConfigs.fatalErrorNotificationTitle": "エージェント構成の削除エラー",
|
||||
"xpack.ingestManager.deleteAgentConfigs.successMultipleNotificationTitle": "{count} 件のエージェント構成を削除しました",
|
||||
"xpack.ingestManager.deleteAgentConfigs.successSingleNotificationTitle": "エージェント構成「{id}」を削除しました",
|
||||
"xpack.ingestManager.deleteApiKeys.confirmModal.cancelButtonLabel": "キャンセル",
|
||||
"xpack.ingestManager.deleteDatasource.confirmModal.affectedAgentsMessage": "{agentConfigName} が一部のエージェントで既に使用されていることをフリートが検出しました。",
|
||||
"xpack.ingestManager.deleteDatasource.confirmModal.affectedAgentsTitle": "このアクションは {agentsCount} {agentsCount, plural, one {# エージェント} other {# エージェント}}に影響します",
|
||||
"xpack.ingestManager.deleteDatasource.confirmModal.cancelButtonLabel": "キャンセル",
|
||||
|
@ -8393,11 +8372,6 @@
|
|||
"xpack.ingestManager.deleteDatasource.successSingleNotificationTitle": "データソース「{id}」を削除しました",
|
||||
"xpack.ingestManager.disabledSecurityDescription": "Elastic Fleet を使用するには、Kibana と Elasticsearch でセキュリティを有効にする必要があります。",
|
||||
"xpack.ingestManager.disabledSecurityTitle": "セキュリティが有効ではありません",
|
||||
"xpack.ingestManager.editConfig.cancelButtonLabel": "キャンセル",
|
||||
"xpack.ingestManager.editConfig.errorNotificationTitle": "エージェント構成を作成できません",
|
||||
"xpack.ingestManager.editConfig.flyoutTitle": "構成を編集",
|
||||
"xpack.ingestManager.editConfig.submitButtonLabel": "更新",
|
||||
"xpack.ingestManager.editConfig.successNotificationTitle": "エージェント構成「{name}」を更新しました",
|
||||
"xpack.ingestManager.enrollmentApiKeyForm.namePlaceholder": "名前を選択",
|
||||
"xpack.ingestManager.enrollmentApiKeyList.createNewButton": "新規キーを作成",
|
||||
"xpack.ingestManager.enrollmentApiKeyList.useExistingsButton": "既存のキーを使用",
|
||||
|
|
|
@ -8320,9 +8320,6 @@
|
|||
"xpack.ingestManager.configDetails.datasourcesTable.namespaceColumnTitle": "命名空间",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.packageNameColumnTitle": "软件包",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.streamsCountColumnTitle": "流计数",
|
||||
"xpack.ingestManager.configDetails.subTabs.datasouces": "数据源",
|
||||
"xpack.ingestManager.configDetails.subTabs.settings": "设置",
|
||||
"xpack.ingestManager.configDetails.subTabs.yamlFile": "YAML 文件",
|
||||
"xpack.ingestManager.configDetails.summary.datasources": "数据源",
|
||||
"xpack.ingestManager.configDetails.summary.lastUpdated": "最后更新时间",
|
||||
"xpack.ingestManager.configDetails.summary.revision": "修订",
|
||||
|
@ -8332,10 +8329,6 @@
|
|||
"xpack.ingestManager.configDetailsDatasources.createFirstButtonText": "创建数据源",
|
||||
"xpack.ingestManager.configDetailsDatasources.createFirstMessage": "此配置尚未有任何数据源。",
|
||||
"xpack.ingestManager.configDetailsDatasources.createFirstTitle": "创建您的首个数据源",
|
||||
"xpack.ingestManager.configForm.descriptionFieldLabel": "描述",
|
||||
"xpack.ingestManager.configForm.nameFieldLabel": "名称",
|
||||
"xpack.ingestManager.configForm.nameRequiredErrorMessage": "配置名称必填",
|
||||
"xpack.ingestManager.configForm.namespaceFieldLabel": "命名空间",
|
||||
"xpack.ingestManager.createAgentConfig.cancelButtonLabel": "取消",
|
||||
"xpack.ingestManager.createAgentConfig.errorNotificationTitle": "无法创建代理配置",
|
||||
"xpack.ingestManager.createAgentConfig.flyoutTitle": "创建代理配置",
|
||||
|
@ -8367,20 +8360,6 @@
|
|||
"xpack.ingestManager.createDatasource.stepSelectPackage.errorLoadingPackagesTitle": "加载软件包时出错",
|
||||
"xpack.ingestManager.createDatasource.stepSelectPackage.errorLoadingSelectedPackageTitle": "加载选定软件包时出错",
|
||||
"xpack.ingestManager.createDatasource.stepSelectPackage.filterPackagesInputPlaceholder": "搜索软件包",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.affectedAgentsMessage": "{agentsCount, plural, one {# 个代理} other {# 个代理}}已分配{agentConfigsCount, plural, one {给此代理配置} other {给这些代理配置}}。将取消注册{agentsCount, plural, one {此代理} other {这些代理}}。",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.cancelButtonLabel": "取消",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.confirmAndReassignButtonLabel": "删除{agentConfigsCount, plural, one {代理配置} other {代理配置}}并取消注册{agentsCount, plural, one {代理} other {代理}}",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.confirmButtonLabel": "删除{agentConfigsCount, plural, one {代理配置} other {代理配置}}",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.deleteMultipleTitle": "删除{count, plural, one {此代理配置} other {# 个代理配置}}?",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.loadingAgentsCountMessage": "正在检查受影响代理数量……",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.loadingButtonLabel": "正在加载……",
|
||||
"xpack.ingestManager.deleteAgentConfigs.confirmModal.noAffectedAgentsMessage": "没有代理分配给{agentConfigsCount, plural, one {此代理配置} other {这些代理配置}}。",
|
||||
"xpack.ingestManager.deleteAgentConfigs.failureMultipleNotificationTitle": "删除 {count} 个代理配置时出错",
|
||||
"xpack.ingestManager.deleteAgentConfigs.failureSingleNotificationTitle": "删除代理配置“{id}”时出错",
|
||||
"xpack.ingestManager.deleteAgentConfigs.fatalErrorNotificationTitle": "删除代理配置时出错",
|
||||
"xpack.ingestManager.deleteAgentConfigs.successMultipleNotificationTitle": "已删除 {count} 个代理配置",
|
||||
"xpack.ingestManager.deleteAgentConfigs.successSingleNotificationTitle": "已删除代理配置“{id}”",
|
||||
"xpack.ingestManager.deleteApiKeys.confirmModal.cancelButtonLabel": "取消",
|
||||
"xpack.ingestManager.deleteDatasource.confirmModal.affectedAgentsMessage": "Fleet 已检测到 {agentConfigName} 已由您的部分代理使用。",
|
||||
"xpack.ingestManager.deleteDatasource.confirmModal.affectedAgentsTitle": "此操作将影响 {agentsCount} 个 {agentsCount, plural, one {代理} other {代理}}。",
|
||||
"xpack.ingestManager.deleteDatasource.confirmModal.cancelButtonLabel": "取消",
|
||||
|
@ -8396,11 +8375,6 @@
|
|||
"xpack.ingestManager.deleteDatasource.successSingleNotificationTitle": "已删除数据源“{id}”",
|
||||
"xpack.ingestManager.disabledSecurityDescription": "必须在 Kibana 和 Elasticsearch 启用安全性,才能使用 Elastic Fleet。",
|
||||
"xpack.ingestManager.disabledSecurityTitle": "安全性未启用",
|
||||
"xpack.ingestManager.editConfig.cancelButtonLabel": "取消",
|
||||
"xpack.ingestManager.editConfig.errorNotificationTitle": "无法更新代理配置",
|
||||
"xpack.ingestManager.editConfig.flyoutTitle": "编辑配置",
|
||||
"xpack.ingestManager.editConfig.submitButtonLabel": "更新",
|
||||
"xpack.ingestManager.editConfig.successNotificationTitle": "代理配置“{name}”已更新",
|
||||
"xpack.ingestManager.enrollmentApiKeyForm.namePlaceholder": "选择名称",
|
||||
"xpack.ingestManager.enrollmentApiKeyList.createNewButton": "创建新密钥",
|
||||
"xpack.ingestManager.enrollmentApiKeyList.useExistingsButton": "使用现有密钥",
|
||||
|
|
Loading…
Reference in a new issue