mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Ingest] Edit datasource UI (#64727)
* Adjust NewDatasource type to exclude stream `agent_stream` property, add additional datasource hooks * Initial pass at edit datasource UI * Clean up dupe code, fix submit button not enabled after re-selecting a package * Remove delete config functionality from list page * Show validation errors for data source name and description fields * Fix types * Add success toasts * Review fixes, clean up i18n Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
9b65cbd92b
commit
fba5128bd8
18 changed files with 462 additions and 198 deletions
|
@ -3,11 +3,12 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { Datasource, NewDatasource, DatasourceInput } from '../types';
|
||||
import { Datasource, DatasourceInput } from '../types';
|
||||
import { storedDatasourceToAgentDatasource } from './datasource_to_agent_datasource';
|
||||
|
||||
describe('Ingest Manager - storedDatasourceToAgentDatasource', () => {
|
||||
const mockNewDatasource: NewDatasource = {
|
||||
const mockDatasource: Datasource = {
|
||||
id: 'some-uuid',
|
||||
name: 'mock-datasource',
|
||||
description: '',
|
||||
config_id: '',
|
||||
|
@ -15,11 +16,6 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => {
|
|||
output_id: '',
|
||||
namespace: 'default',
|
||||
inputs: [],
|
||||
};
|
||||
|
||||
const mockDatasource: Datasource = {
|
||||
...mockNewDatasource,
|
||||
id: 'some-uuid',
|
||||
revision: 1,
|
||||
};
|
||||
|
||||
|
@ -107,17 +103,6 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('uses name for id when id is not provided in case of new datasource', () => {
|
||||
expect(storedDatasourceToAgentDatasource(mockNewDatasource)).toEqual({
|
||||
id: 'mock-datasource',
|
||||
name: 'mock-datasource',
|
||||
namespace: 'default',
|
||||
enabled: true,
|
||||
use_output: 'default',
|
||||
inputs: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns agent datasource config with flattened input and package stream', () => {
|
||||
expect(storedDatasourceToAgentDatasource({ ...mockDatasource, inputs: [mockInput] })).toEqual({
|
||||
id: 'some-uuid',
|
||||
|
|
|
@ -3,16 +3,16 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { Datasource, NewDatasource, FullAgentConfigDatasource } from '../types';
|
||||
import { Datasource, FullAgentConfigDatasource } from '../types';
|
||||
import { DEFAULT_OUTPUT } from '../constants';
|
||||
|
||||
export const storedDatasourceToAgentDatasource = (
|
||||
datasource: Datasource | NewDatasource
|
||||
datasource: Datasource
|
||||
): FullAgentConfigDatasource => {
|
||||
const { name, namespace, enabled, package: pkg, inputs } = datasource;
|
||||
const { id, name, namespace, enabled, package: pkg, inputs } = datasource;
|
||||
|
||||
const fullDatasource: FullAgentConfigDatasource = {
|
||||
id: 'id' in datasource ? datasource.id : name,
|
||||
id: id || name,
|
||||
name,
|
||||
namespace,
|
||||
enabled,
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectAttributes } from 'src/core/public';
|
||||
import {
|
||||
Datasource,
|
||||
DatasourcePackage,
|
||||
|
@ -26,7 +24,7 @@ export interface NewAgentConfig {
|
|||
monitoring_enabled?: Array<'logs' | 'metrics'>;
|
||||
}
|
||||
|
||||
export interface AgentConfig extends NewAgentConfig, SavedObjectAttributes {
|
||||
export interface AgentConfig extends NewAgentConfig {
|
||||
id: string;
|
||||
status: AgentConfigStatus;
|
||||
datasources: string[] | Datasource[];
|
||||
|
|
|
@ -17,22 +17,29 @@ export interface DatasourceConfigRecordEntry {
|
|||
|
||||
export type DatasourceConfigRecord = Record<string, DatasourceConfigRecordEntry>;
|
||||
|
||||
export interface DatasourceInputStream {
|
||||
export interface NewDatasourceInputStream {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
dataset: string;
|
||||
processors?: string[];
|
||||
config?: DatasourceConfigRecord;
|
||||
vars?: DatasourceConfigRecord;
|
||||
}
|
||||
|
||||
export interface DatasourceInputStream extends NewDatasourceInputStream {
|
||||
agent_stream?: any;
|
||||
}
|
||||
|
||||
export interface DatasourceInput {
|
||||
export interface NewDatasourceInput {
|
||||
type: string;
|
||||
enabled: boolean;
|
||||
processors?: string[];
|
||||
config?: DatasourceConfigRecord;
|
||||
vars?: DatasourceConfigRecord;
|
||||
streams: NewDatasourceInputStream[];
|
||||
}
|
||||
|
||||
export interface DatasourceInput extends Omit<NewDatasourceInput, 'streams'> {
|
||||
streams: DatasourceInputStream[];
|
||||
}
|
||||
|
||||
|
@ -44,10 +51,11 @@ export interface NewDatasource {
|
|||
enabled: boolean;
|
||||
package?: DatasourcePackage;
|
||||
output_id: string;
|
||||
inputs: DatasourceInput[];
|
||||
inputs: NewDatasourceInput[];
|
||||
}
|
||||
|
||||
export type Datasource = NewDatasource & {
|
||||
export interface Datasource extends Omit<NewDatasource, 'inputs'> {
|
||||
id: string;
|
||||
inputs: DatasourceInput[];
|
||||
revision: number;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,12 +5,18 @@
|
|||
*/
|
||||
import { sendRequest, useRequest } from './use_request';
|
||||
import { datasourceRouteService } from '../../services';
|
||||
import { CreateDatasourceRequest, CreateDatasourceResponse } from '../../types';
|
||||
import {
|
||||
CreateDatasourceRequest,
|
||||
CreateDatasourceResponse,
|
||||
UpdateDatasourceRequest,
|
||||
UpdateDatasourceResponse,
|
||||
} from '../../types';
|
||||
import {
|
||||
DeleteDatasourcesRequest,
|
||||
DeleteDatasourcesResponse,
|
||||
GetDatasourcesRequest,
|
||||
GetDatasourcesResponse,
|
||||
GetOneDatasourceResponse,
|
||||
} from '../../../../../common/types/rest_spec';
|
||||
|
||||
export const sendCreateDatasource = (body: CreateDatasourceRequest['body']) => {
|
||||
|
@ -21,6 +27,17 @@ export const sendCreateDatasource = (body: CreateDatasourceRequest['body']) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const sendUpdateDatasource = (
|
||||
datasourceId: string,
|
||||
body: UpdateDatasourceRequest['body']
|
||||
) => {
|
||||
return sendRequest<UpdateDatasourceResponse>({
|
||||
path: datasourceRouteService.getUpdatePath(datasourceId),
|
||||
method: 'put',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
};
|
||||
|
||||
export const sendDeleteDatasource = (body: DeleteDatasourcesRequest['body']) => {
|
||||
return sendRequest<DeleteDatasourcesResponse>({
|
||||
path: datasourceRouteService.getDeletePath(),
|
||||
|
@ -36,3 +53,10 @@ export function useGetDatasources(query: GetDatasourcesRequest['query']) {
|
|||
query,
|
||||
});
|
||||
}
|
||||
|
||||
export const sendGetOneDatasource = (datasourceId: string) => {
|
||||
return sendRequest<GetOneDatasourceResponse>({
|
||||
path: datasourceRouteService.getInfoPath(datasourceId),
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
|
|
@ -39,17 +39,29 @@ export const CreateDatasourcePageLayout: React.FunctionComponent<{
|
|||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createDatasource.pageTitle"
|
||||
defaultMessage="Add data source"
|
||||
/>
|
||||
{from === 'edit' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editDatasource.pageTitle"
|
||||
defaultMessage="Edit data source"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createDatasource.pageTitle"
|
||||
defaultMessage="Add data source"
|
||||
/>
|
||||
)}
|
||||
</h1>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText color="subdued" size="s">
|
||||
{from === 'config' ? (
|
||||
{from === 'edit' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editDatasource.pageDescription"
|
||||
defaultMessage="Follow the instructions below to edit this data source."
|
||||
/>
|
||||
) : from === 'config' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createDatasource.pageDescriptionfromConfig"
|
||||
defaultMessage="Follow the instructions below to add an integration to this agent configuration."
|
||||
|
@ -68,7 +80,7 @@ export const CreateDatasourcePageLayout: React.FunctionComponent<{
|
|||
<EuiFlexGroup justifyContent="flexEnd" direction={'row'} gutterSize="xl">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSpacer size="s" />
|
||||
{agentConfig && from === 'config' ? (
|
||||
{agentConfig && (from === 'config' || from === 'edit') ? (
|
||||
<EuiDescriptionList style={{ textAlign: 'right' }} textStyle="reverse">
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
} from '../../../hooks';
|
||||
import { useLinks as useEPMLinks } from '../../epm/hooks';
|
||||
import { CreateDatasourcePageLayout, ConfirmCreateDatasourceModal } from './components';
|
||||
import { CreateDatasourceFrom } from './types';
|
||||
import { CreateDatasourceFrom, DatasourceFormState } from './types';
|
||||
import { DatasourceValidationResults, validateDatasource, validationHasErrors } from './services';
|
||||
import { StepSelectPackage } from './step_select_package';
|
||||
import { StepSelectConfig } from './step_select_config';
|
||||
|
@ -85,6 +85,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => {
|
|||
const updatePackageInfo = (updatedPackageInfo: PackageInfo | undefined) => {
|
||||
if (updatedPackageInfo) {
|
||||
setPackageInfo(updatedPackageInfo);
|
||||
setFormState('VALID');
|
||||
} else {
|
||||
setFormState('INVALID');
|
||||
setPackageInfo(undefined);
|
||||
|
@ -152,9 +153,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => {
|
|||
const cancelUrl = from === 'config' ? CONFIG_URL : PACKAGE_URL;
|
||||
|
||||
// Save datasource
|
||||
const [formState, setFormState] = useState<
|
||||
'VALID' | 'INVALID' | 'CONFIRM' | 'LOADING' | 'SUBMITTED'
|
||||
>('INVALID');
|
||||
const [formState, setFormState] = useState<DatasourceFormState>('INVALID');
|
||||
const saveDatasource = async () => {
|
||||
setFormState('LOADING');
|
||||
const result = await sendCreateDatasource(datasource);
|
||||
|
@ -174,6 +173,23 @@ export const CreateDatasourcePage: React.FunctionComponent = () => {
|
|||
const { error } = await saveDatasource();
|
||||
if (!error) {
|
||||
history.push(`${AGENT_CONFIG_DETAILS_PATH}${agentConfig ? agentConfig.id : configId}`);
|
||||
notifications.toasts.addSuccess({
|
||||
title: i18n.translate('xpack.ingestManager.createDatasource.addedNotificationTitle', {
|
||||
defaultMessage: `Successfully added '{datasourceName}'`,
|
||||
values: {
|
||||
datasourceName: datasource.name,
|
||||
},
|
||||
}),
|
||||
text:
|
||||
agentCount && agentConfig
|
||||
? i18n.translate('xpack.ingestManager.createDatasource.addedNotificationMessage', {
|
||||
defaultMessage: `Fleet will deploy updates to all agents that use the '{agentConfigName}' configuration`,
|
||||
values: {
|
||||
agentConfigName: agentConfig.name,
|
||||
},
|
||||
})
|
||||
: undefined,
|
||||
});
|
||||
} else {
|
||||
notifications.toasts.addError(error, {
|
||||
title: 'Error',
|
||||
|
@ -229,6 +245,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => {
|
|||
packageInfo={packageInfo}
|
||||
datasource={datasource}
|
||||
updateDatasource={updateDatasource}
|
||||
validationResults={validationResults!}
|
||||
/>
|
||||
) : null,
|
||||
},
|
||||
|
@ -240,7 +257,6 @@ export const CreateDatasourcePage: React.FunctionComponent = () => {
|
|||
children:
|
||||
agentConfig && packageInfo ? (
|
||||
<StepConfigureDatasource
|
||||
agentConfig={agentConfig}
|
||||
packageInfo={packageInfo}
|
||||
datasource={datasource}
|
||||
updateDatasource={updateDatasource}
|
||||
|
|
|
@ -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, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiPanel,
|
||||
|
@ -15,75 +15,21 @@ import {
|
|||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
AgentConfig,
|
||||
PackageInfo,
|
||||
Datasource,
|
||||
NewDatasource,
|
||||
DatasourceInput,
|
||||
} from '../../../types';
|
||||
import { PackageInfo, NewDatasource, DatasourceInput } from '../../../types';
|
||||
import { Loading } from '../../../components';
|
||||
import { packageToConfigDatasourceInputs } from '../../../services';
|
||||
import { DatasourceValidationResults, validationHasErrors } from './services';
|
||||
import { DatasourceInputPanel } from './components';
|
||||
|
||||
export const StepConfigureDatasource: React.FunctionComponent<{
|
||||
agentConfig: AgentConfig;
|
||||
packageInfo: PackageInfo;
|
||||
datasource: NewDatasource;
|
||||
updateDatasource: (fields: Partial<NewDatasource>) => void;
|
||||
validationResults: DatasourceValidationResults;
|
||||
submitAttempted: boolean;
|
||||
}> = ({
|
||||
agentConfig,
|
||||
packageInfo,
|
||||
datasource,
|
||||
updateDatasource,
|
||||
validationResults,
|
||||
submitAttempted,
|
||||
}) => {
|
||||
// Form show/hide states
|
||||
|
||||
}> = ({ packageInfo, datasource, updateDatasource, validationResults, submitAttempted }) => {
|
||||
const hasErrors = validationResults ? validationHasErrors(validationResults) : false;
|
||||
|
||||
// Update datasource's package and config info
|
||||
useEffect(() => {
|
||||
const dsPackage = datasource.package;
|
||||
const currentPkgKey = dsPackage ? `${dsPackage.name}-${dsPackage.version}` : '';
|
||||
const pkgKey = `${packageInfo.name}-${packageInfo.version}`;
|
||||
|
||||
// If package has changed, create shell datasource with input&stream values based on package info
|
||||
if (currentPkgKey !== pkgKey) {
|
||||
// Existing datasources on the agent config using the package name, retrieve highest number appended to datasource name
|
||||
const dsPackageNamePattern = new RegExp(`${packageInfo.name}-(\\d+)`);
|
||||
const dsWithMatchingNames = (agentConfig.datasources as Datasource[])
|
||||
.filter(ds => Boolean(ds.name.match(dsPackageNamePattern)))
|
||||
.map(ds => parseInt(ds.name.match(dsPackageNamePattern)![1], 10))
|
||||
.sort();
|
||||
|
||||
updateDatasource({
|
||||
name: `${packageInfo.name}-${
|
||||
dsWithMatchingNames.length ? dsWithMatchingNames[dsWithMatchingNames.length - 1] + 1 : 1
|
||||
}`,
|
||||
package: {
|
||||
name: packageInfo.name,
|
||||
title: packageInfo.title,
|
||||
version: packageInfo.version,
|
||||
},
|
||||
inputs: packageToConfigDatasourceInputs(packageInfo),
|
||||
});
|
||||
}
|
||||
|
||||
// If agent config has changed, update datasource's config ID and namespace
|
||||
if (datasource.config_id !== agentConfig.id) {
|
||||
updateDatasource({
|
||||
config_id: agentConfig.id,
|
||||
namespace: agentConfig.namespace,
|
||||
});
|
||||
}
|
||||
}, [datasource.package, datasource.config_id, agentConfig, packageInfo, updateDatasource]);
|
||||
|
||||
// Step B, configure inputs (and their streams)
|
||||
// Configure inputs (and their streams)
|
||||
// Assume packages only export one datasource for now
|
||||
const renderConfigureInputs = () =>
|
||||
packageInfo.datasources &&
|
||||
|
|
|
@ -17,13 +17,16 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { AgentConfig, PackageInfo, Datasource, NewDatasource } from '../../../types';
|
||||
import { packageToConfigDatasourceInputs } from '../../../services';
|
||||
import { Loading } from '../../../components';
|
||||
import { DatasourceValidationResults } from './services';
|
||||
|
||||
export const StepDefineDatasource: React.FunctionComponent<{
|
||||
agentConfig: AgentConfig;
|
||||
packageInfo: PackageInfo;
|
||||
datasource: NewDatasource;
|
||||
updateDatasource: (fields: Partial<NewDatasource>) => void;
|
||||
}> = ({ agentConfig, packageInfo, datasource, updateDatasource }) => {
|
||||
validationResults: DatasourceValidationResults;
|
||||
}> = ({ agentConfig, packageInfo, datasource, updateDatasource, validationResults }) => {
|
||||
// Form show/hide states
|
||||
const [isShowingAdvancedDefine, setIsShowingAdvancedDefine] = useState<boolean>(false);
|
||||
|
||||
|
@ -64,11 +67,13 @@ export const StepDefineDatasource: React.FunctionComponent<{
|
|||
}
|
||||
}, [datasource.package, datasource.config_id, agentConfig, packageInfo, updateDatasource]);
|
||||
|
||||
return (
|
||||
return validationResults ? (
|
||||
<>
|
||||
<EuiFlexGrid columns={2}>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
isInvalid={!!validationResults.name}
|
||||
error={validationResults.name}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createDatasource.stepConfigure.datasourceNameInputLabel"
|
||||
|
@ -102,6 +107,8 @@ export const StepDefineDatasource: React.FunctionComponent<{
|
|||
/>
|
||||
</EuiText>
|
||||
}
|
||||
isInvalid={!!validationResults.description}
|
||||
error={validationResults.description}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={datasource.description}
|
||||
|
@ -161,5 +168,7 @@ export const StepDefineDatasource: React.FunctionComponent<{
|
|||
</Fragment>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<Loading />
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export type CreateDatasourceFrom = 'package' | 'config';
|
||||
export type CreateDatasourceFrom = 'package' | 'config' | 'edit';
|
||||
export type DatasourceFormState = 'VALID' | 'INVALID' | 'CONFIRM' | 'LOADING' | 'SUBMITTED';
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
import { AgentConfig, Datasource } from '../../../../../types';
|
||||
import { TableRowActions } from '../../../components/table_row_actions';
|
||||
import { DangerEuiContextMenuItem } from '../../../components/danger_eui_context_menu_item';
|
||||
import { useCapabilities } from '../../../../../hooks';
|
||||
import { useCapabilities, useLink } from '../../../../../hooks';
|
||||
import { useAgentConfigLink } from '../../hooks/use_details_uri';
|
||||
import { DatasourceDeleteProvider } from '../../../components/datasource_delete_provider';
|
||||
import { useConfigRefresh } from '../../hooks/use_config';
|
||||
|
@ -56,6 +56,7 @@ export const DatasourcesTable: React.FunctionComponent<Props> = ({
|
|||
}) => {
|
||||
const hasWriteCapabilities = useCapabilities().write;
|
||||
const addDatasourceLink = useAgentConfigLink('add-datasource', { configId: config.id });
|
||||
const editDatasourceLink = useLink(`/configs/${config.id}/edit-datasource`);
|
||||
const refreshConfig = useConfigRefresh();
|
||||
|
||||
// With the datasources provided on input, generate the list of datasources
|
||||
|
@ -201,22 +202,21 @@ export const DatasourcesTable: React.FunctionComponent<Props> = ({
|
|||
<TableRowActions
|
||||
items={[
|
||||
// FIXME: implement View datasource action
|
||||
// <EuiContextMenuItem
|
||||
// disabled
|
||||
// icon="inspect"
|
||||
// onClick={() => {}}
|
||||
// key="datasourceView"
|
||||
// >
|
||||
// <FormattedMessage
|
||||
// id="xpack.ingestManager.configDetails.datasourcesTable.viewActionTitle"
|
||||
// defaultMessage="View data source"
|
||||
// />
|
||||
// </EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
disabled
|
||||
icon="inspect"
|
||||
onClick={() => {}}
|
||||
key="datasourceView"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.configDetails.datasourcesTable.viewActionTitle"
|
||||
defaultMessage="View data source"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
// FIXME: implement Edit datasource action
|
||||
<EuiContextMenuItem
|
||||
disabled
|
||||
disabled={!hasWriteCapabilities}
|
||||
icon="pencil"
|
||||
onClick={() => {}}
|
||||
href={`${editDatasourceLink}/${datasource.id}`}
|
||||
key="datasourceEdit"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -225,12 +225,12 @@ export const DatasourcesTable: React.FunctionComponent<Props> = ({
|
|||
/>
|
||||
</EuiContextMenuItem>,
|
||||
// FIXME: implement Copy datasource action
|
||||
<EuiContextMenuItem disabled icon="copy" onClick={() => {}} key="datasourceCopy">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.configDetails.datasourcesTable.copyActionTitle"
|
||||
defaultMessage="Copy data source"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
// <EuiContextMenuItem disabled icon="copy" onClick={() => {}} key="datasourceCopy">
|
||||
// <FormattedMessage
|
||||
// id="xpack.ingestManager.configDetails.datasourcesTable.copyActionTitle"
|
||||
// defaultMessage="Copy data source"
|
||||
// />
|
||||
// </EuiContextMenuItem>,
|
||||
<DatasourceDeleteProvider agentConfig={config} key="datasourceDelete">
|
||||
{deleteDatasourcePrompt => {
|
||||
return (
|
||||
|
@ -256,7 +256,7 @@ export const DatasourcesTable: React.FunctionComponent<Props> = ({
|
|||
],
|
||||
},
|
||||
],
|
||||
[config, hasWriteCapabilities, refreshConfig]
|
||||
[config, editDatasourceLink, hasWriteCapabilities, refreshConfig]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,323 @@
|
|||
/*
|
||||
* 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, useEffect } from 'react';
|
||||
import { useRouteMatch, useHistory } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiButton,
|
||||
EuiSteps,
|
||||
EuiBottomBar,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { AGENT_CONFIG_DETAILS_PATH } from '../../../constants';
|
||||
import { AgentConfig, PackageInfo, NewDatasource } from '../../../types';
|
||||
import {
|
||||
useLink,
|
||||
useCore,
|
||||
useConfig,
|
||||
sendUpdateDatasource,
|
||||
sendGetAgentStatus,
|
||||
sendGetOneAgentConfig,
|
||||
sendGetOneDatasource,
|
||||
sendGetPackageInfoByKey,
|
||||
} from '../../../hooks';
|
||||
import { Loading, Error } from '../../../components';
|
||||
import {
|
||||
CreateDatasourcePageLayout,
|
||||
ConfirmCreateDatasourceModal,
|
||||
} from '../create_datasource_page/components';
|
||||
import {
|
||||
DatasourceValidationResults,
|
||||
validateDatasource,
|
||||
validationHasErrors,
|
||||
} from '../create_datasource_page/services';
|
||||
import { DatasourceFormState, CreateDatasourceFrom } from '../create_datasource_page/types';
|
||||
import { StepConfigureDatasource } from '../create_datasource_page/step_configure_datasource';
|
||||
import { StepDefineDatasource } from '../create_datasource_page/step_define_datasource';
|
||||
|
||||
export const EditDatasourcePage: React.FunctionComponent = () => {
|
||||
const { notifications } = useCore();
|
||||
const {
|
||||
fleet: { enabled: isFleetEnabled },
|
||||
} = useConfig();
|
||||
const {
|
||||
params: { configId, datasourceId },
|
||||
} = useRouteMatch();
|
||||
const history = useHistory();
|
||||
|
||||
// Agent config, package info, and datasource states
|
||||
const [isLoadingData, setIsLoadingData] = useState<boolean>(true);
|
||||
const [loadingError, setLoadingError] = useState<Error>();
|
||||
const [agentConfig, setAgentConfig] = useState<AgentConfig>();
|
||||
const [packageInfo, setPackageInfo] = useState<PackageInfo>();
|
||||
const [datasource, setDatasource] = useState<NewDatasource>({
|
||||
name: '',
|
||||
description: '',
|
||||
config_id: '',
|
||||
enabled: true,
|
||||
output_id: '',
|
||||
inputs: [],
|
||||
});
|
||||
|
||||
// Retrieve agent config, package, and datasource info
|
||||
useEffect(() => {
|
||||
const getData = async () => {
|
||||
setIsLoadingData(true);
|
||||
setLoadingError(undefined);
|
||||
try {
|
||||
const [{ data: agentConfigData }, { data: datasourceData }] = await Promise.all([
|
||||
sendGetOneAgentConfig(configId),
|
||||
sendGetOneDatasource(datasourceId),
|
||||
]);
|
||||
if (agentConfigData?.item) {
|
||||
setAgentConfig(agentConfigData.item);
|
||||
}
|
||||
if (datasourceData?.item) {
|
||||
const { id, revision, inputs, ...restOfDatasource } = datasourceData.item;
|
||||
// Remove `agent_stream` from all stream info, we assign this after saving
|
||||
const newDatasource = {
|
||||
...restOfDatasource,
|
||||
inputs: inputs.map(input => {
|
||||
const { streams, ...restOfInput } = input;
|
||||
return {
|
||||
...restOfInput,
|
||||
streams: streams.map(stream => {
|
||||
const { agent_stream, ...restOfStream } = stream;
|
||||
return restOfStream;
|
||||
}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
setDatasource(newDatasource);
|
||||
if (datasourceData.item.package) {
|
||||
const { data: packageData } = await sendGetPackageInfoByKey(
|
||||
`${datasourceData.item.package.name}-${datasourceData.item.package.version}`
|
||||
);
|
||||
if (packageData?.response) {
|
||||
setPackageInfo(packageData.response);
|
||||
setValidationResults(validateDatasource(newDatasource, packageData.response));
|
||||
setFormState('VALID');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
setLoadingError(e);
|
||||
}
|
||||
setIsLoadingData(false);
|
||||
};
|
||||
getData();
|
||||
}, [configId, datasourceId]);
|
||||
|
||||
// Retrieve agent count
|
||||
const [agentCount, setAgentCount] = useState<number>(0);
|
||||
useEffect(() => {
|
||||
const getAgentCount = async () => {
|
||||
const { data } = await sendGetAgentStatus({ configId });
|
||||
if (data?.results.total) {
|
||||
setAgentCount(data.results.total);
|
||||
}
|
||||
};
|
||||
|
||||
if (isFleetEnabled) {
|
||||
getAgentCount();
|
||||
}
|
||||
}, [configId, isFleetEnabled]);
|
||||
|
||||
// Datasource validation state
|
||||
const [validationResults, setValidationResults] = useState<DatasourceValidationResults>();
|
||||
const hasErrors = validationResults ? validationHasErrors(validationResults) : false;
|
||||
|
||||
// Update datasource method
|
||||
const updateDatasource = (updatedFields: Partial<NewDatasource>) => {
|
||||
const newDatasource = {
|
||||
...datasource,
|
||||
...updatedFields,
|
||||
};
|
||||
setDatasource(newDatasource);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('Datasource updated', newDatasource);
|
||||
const newValidationResults = updateDatasourceValidation(newDatasource);
|
||||
const hasValidationErrors = newValidationResults
|
||||
? validationHasErrors(newValidationResults)
|
||||
: false;
|
||||
if (!hasValidationErrors) {
|
||||
setFormState('VALID');
|
||||
}
|
||||
};
|
||||
|
||||
const updateDatasourceValidation = (newDatasource?: NewDatasource) => {
|
||||
if (packageInfo) {
|
||||
const newValidationResult = validateDatasource(newDatasource || datasource, packageInfo);
|
||||
setValidationResults(newValidationResult);
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('Datasource validation results', newValidationResult);
|
||||
|
||||
return newValidationResult;
|
||||
}
|
||||
};
|
||||
|
||||
// Cancel url
|
||||
const CONFIG_URL = useLink(`${AGENT_CONFIG_DETAILS_PATH}${configId}`);
|
||||
const cancelUrl = CONFIG_URL;
|
||||
|
||||
// Save datasource
|
||||
const [formState, setFormState] = useState<DatasourceFormState>('INVALID');
|
||||
const saveDatasource = async () => {
|
||||
setFormState('LOADING');
|
||||
const result = await sendUpdateDatasource(datasourceId, datasource);
|
||||
setFormState('SUBMITTED');
|
||||
return result;
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (formState === 'VALID' && hasErrors) {
|
||||
setFormState('INVALID');
|
||||
return;
|
||||
}
|
||||
if (agentCount !== 0 && formState !== 'CONFIRM') {
|
||||
setFormState('CONFIRM');
|
||||
return;
|
||||
}
|
||||
const { error } = await saveDatasource();
|
||||
if (!error) {
|
||||
history.push(`${AGENT_CONFIG_DETAILS_PATH}${configId}`);
|
||||
notifications.toasts.addSuccess({
|
||||
title: i18n.translate('xpack.ingestManager.editDatasource.updatedNotificationTitle', {
|
||||
defaultMessage: `Successfully updated '{datasourceName}'`,
|
||||
values: {
|
||||
datasourceName: datasource.name,
|
||||
},
|
||||
}),
|
||||
text:
|
||||
agentCount && agentConfig
|
||||
? i18n.translate('xpack.ingestManager.editDatasource.updatedNotificationMessage', {
|
||||
defaultMessage: `Fleet will deploy updates to all agents that use the '{agentConfigName}' configuration`,
|
||||
values: {
|
||||
agentConfigName: agentConfig.name,
|
||||
},
|
||||
})
|
||||
: undefined,
|
||||
});
|
||||
} else {
|
||||
notifications.toasts.addError(error, {
|
||||
title: 'Error',
|
||||
});
|
||||
setFormState('VALID');
|
||||
}
|
||||
};
|
||||
|
||||
const layoutProps = {
|
||||
from: 'edit' as CreateDatasourceFrom,
|
||||
cancelUrl,
|
||||
agentConfig,
|
||||
packageInfo,
|
||||
};
|
||||
|
||||
return (
|
||||
<CreateDatasourcePageLayout {...layoutProps}>
|
||||
{isLoadingData ? (
|
||||
<Loading />
|
||||
) : loadingError || !agentConfig || !packageInfo ? (
|
||||
<Error
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editDatasource.errorLoadingDataTitle"
|
||||
defaultMessage="Error loading data"
|
||||
/>
|
||||
}
|
||||
error={
|
||||
loadingError ||
|
||||
i18n.translate('xpack.ingestManager.editDatasource.errorLoadingDataMessage', {
|
||||
defaultMessage: 'There was an error loading this data source information',
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{formState === 'CONFIRM' && (
|
||||
<ConfirmCreateDatasourceModal
|
||||
agentCount={agentCount}
|
||||
agentConfig={agentConfig}
|
||||
onConfirm={onSubmit}
|
||||
onCancel={() => setFormState('VALID')}
|
||||
/>
|
||||
)}
|
||||
<EuiSteps
|
||||
steps={[
|
||||
{
|
||||
title: i18n.translate(
|
||||
'xpack.ingestManager.editDatasource.stepDefineDatasourceTitle',
|
||||
{
|
||||
defaultMessage: 'Define your data source',
|
||||
}
|
||||
),
|
||||
children: (
|
||||
<StepDefineDatasource
|
||||
agentConfig={agentConfig}
|
||||
packageInfo={packageInfo}
|
||||
datasource={datasource}
|
||||
updateDatasource={updateDatasource}
|
||||
validationResults={validationResults!}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate(
|
||||
'xpack.ingestManager.editDatasource.stepConfgiureDatasourceTitle',
|
||||
{
|
||||
defaultMessage: 'Select the data you want to collect',
|
||||
}
|
||||
),
|
||||
children: (
|
||||
<StepConfigureDatasource
|
||||
packageInfo={packageInfo}
|
||||
datasource={datasource}
|
||||
updateDatasource={updateDatasource}
|
||||
validationResults={validationResults!}
|
||||
submitAttempted={formState === 'INVALID'}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiBottomBar css={{ zIndex: 5 }} paddingSize="s">
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty color="ghost" href={cancelUrl}>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editDatasource.cancelButton"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={onSubmit}
|
||||
isLoading={formState === 'LOADING'}
|
||||
disabled={formState !== 'VALID'}
|
||||
iconType="save"
|
||||
color="primary"
|
||||
fill
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editDatasource.saveButton"
|
||||
defaultMessage="Save data source"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiBottomBar>
|
||||
</>
|
||||
)}
|
||||
</CreateDatasourcePageLayout>
|
||||
);
|
||||
};
|
|
@ -8,10 +8,14 @@ import { HashRouter as Router, Switch, Route } from 'react-router-dom';
|
|||
import { AgentConfigListPage } from './list_page';
|
||||
import { AgentConfigDetailsPage } from './details_page';
|
||||
import { CreateDatasourcePage } from './create_datasource_page';
|
||||
import { EditDatasourcePage } from './edit_datasource_page';
|
||||
|
||||
export const AgentConfigApp: React.FunctionComponent = () => (
|
||||
<Router>
|
||||
<Switch>
|
||||
<Route path="/configs/:configId/edit-datasource/:datasourceId">
|
||||
<EditDatasourcePage />
|
||||
</Route>
|
||||
<Route path="/configs/:configId/add-datasource">
|
||||
<CreateDatasourcePage />
|
||||
</Route>
|
||||
|
|
|
@ -36,13 +36,11 @@ import {
|
|||
useConfig,
|
||||
useUrlParams,
|
||||
} from '../../../hooks';
|
||||
import { AgentConfigDeleteProvider } from '../components';
|
||||
import { CreateAgentConfigFlyout } from './components';
|
||||
import { SearchBar } from '../../../components/search_bar';
|
||||
import { LinkedAgentCount } from '../components';
|
||||
import { useAgentConfigLink } from '../details_page/hooks/use_details_uri';
|
||||
import { TableRowActions } from '../components/table_row_actions';
|
||||
import { DangerEuiContextMenuItem } from '../components/danger_eui_context_menu_item';
|
||||
|
||||
const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({
|
||||
overflow: 'hidden',
|
||||
|
@ -108,30 +106,12 @@ const ConfigRowActions = memo<{ config: AgentConfig; onDelete: () => void }>(
|
|||
defaultMessage="Create data source"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
|
||||
<EuiContextMenuItem disabled={true} icon="copy" key="copyConfig">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigList.copyConfigActionText"
|
||||
defaultMessage="Copy configuration"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
|
||||
<AgentConfigDeleteProvider key="deleteConfig">
|
||||
{deleteAgentConfigsPrompt => {
|
||||
return (
|
||||
<DangerEuiContextMenuItem
|
||||
icon="trash"
|
||||
disabled={Boolean(config.is_default)}
|
||||
onClick={() => deleteAgentConfigsPrompt([config.id], onDelete)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigList.deleteConfigActionText"
|
||||
defaultMessage="Delete Configuration"
|
||||
/>
|
||||
</DangerEuiContextMenuItem>
|
||||
);
|
||||
}}
|
||||
</AgentConfigDeleteProvider>,
|
||||
// <EuiContextMenuItem disabled={true} icon="copy" key="copyConfig">
|
||||
// <FormattedMessage
|
||||
// id="xpack.ingestManager.agentConfigList.copyConfigActionText"
|
||||
// defaultMessage="Copy configuration"
|
||||
// />
|
||||
// </EuiContextMenuItem>,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
@ -156,7 +136,6 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
|
|||
: urlParams.kuery ?? ''
|
||||
);
|
||||
const { pagination, pageSizeOptions, setPagination } = usePagination();
|
||||
const [selectedAgentConfigs, setSelectedAgentConfigs] = useState<AgentConfig[]>([]);
|
||||
const history = useHistory();
|
||||
const isCreateAgentConfigFlyoutOpen = 'create' in urlParams;
|
||||
const setIsCreateAgentConfigFlyoutOpen = useCallback(
|
||||
|
@ -321,34 +300,6 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
|
|||
/>
|
||||
) : null}
|
||||
<EuiFlexGroup alignItems={'center'} gutterSize="m">
|
||||
{selectedAgentConfigs.length ? (
|
||||
<EuiFlexItem>
|
||||
<AgentConfigDeleteProvider>
|
||||
{deleteAgentConfigsPrompt => (
|
||||
<EuiButton
|
||||
color="danger"
|
||||
onClick={() => {
|
||||
deleteAgentConfigsPrompt(
|
||||
selectedAgentConfigs.map(agentConfig => agentConfig.id),
|
||||
() => {
|
||||
sendRequest();
|
||||
setSelectedAgentConfigs([]);
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.agentConfigList.deleteButton"
|
||||
defaultMessage="Delete {count, plural, one {# agent config} other {# agent configs}}"
|
||||
values={{
|
||||
count: selectedAgentConfigs.length,
|
||||
}}
|
||||
/>
|
||||
</EuiButton>
|
||||
)}
|
||||
</AgentConfigDeleteProvider>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexItem grow={4}>
|
||||
<SearchBar
|
||||
value={search}
|
||||
|
@ -405,13 +356,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
|
|||
items={agentConfigData ? agentConfigData.items : []}
|
||||
itemId="id"
|
||||
columns={columns}
|
||||
isSelectable={true}
|
||||
selection={{
|
||||
selectable: (agentConfig: AgentConfig) => !agentConfig.is_default,
|
||||
onSelectionChange: (newSelectedAgentConfigs: AgentConfig[]) => {
|
||||
setSelectedAgentConfigs(newSelectedAgentConfigs);
|
||||
},
|
||||
}}
|
||||
isSelectable={false}
|
||||
pagination={{
|
||||
pageIndex: pagination.currentPage - 1,
|
||||
pageSize: pagination.pageSize,
|
||||
|
|
|
@ -32,6 +32,8 @@ export {
|
|||
// API schemas - Datasource
|
||||
CreateDatasourceRequest,
|
||||
CreateDatasourceResponse,
|
||||
UpdateDatasourceRequest,
|
||||
UpdateDatasourceResponse,
|
||||
// API schemas - Data Streams
|
||||
GetDataStreamsResponse,
|
||||
// API schemas - Agents
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
} from '../../types';
|
||||
import {
|
||||
GetAgentConfigsResponse,
|
||||
GetAgentConfigsResponseItem,
|
||||
GetOneAgentConfigResponse,
|
||||
CreateAgentConfigResponse,
|
||||
UpdateAgentConfigResponse,
|
||||
|
@ -46,7 +47,7 @@ export const getAgentConfigsHandler: RequestHandler<
|
|||
|
||||
await bluebird.map(
|
||||
items,
|
||||
agentConfig =>
|
||||
(agentConfig: GetAgentConfigsResponseItem) =>
|
||||
listAgents(soClient, {
|
||||
showInactive: true,
|
||||
perPage: 0,
|
||||
|
|
|
@ -8225,11 +8225,8 @@
|
|||
"xpack.ingestManager.agentConfigList.addButton": "エージェント構成を作成",
|
||||
"xpack.ingestManager.agentConfigList.agentsColumnTitle": "エージェント",
|
||||
"xpack.ingestManager.agentConfigList.clearFiltersLinkText": "フィルターを消去",
|
||||
"xpack.ingestManager.agentConfigList.copyConfigActionText": "構成をコピー",
|
||||
"xpack.ingestManager.agentConfigList.createDatasourceActionText": "データソースを作成",
|
||||
"xpack.ingestManager.agentConfigList.datasourcesCountColumnTitle": "データソース",
|
||||
"xpack.ingestManager.agentConfigList.deleteButton": "{count, plural, one {# エージェント設定} other {# エージェント設定}}を削除",
|
||||
"xpack.ingestManager.agentConfigList.deleteConfigActionText": "構成の削除",
|
||||
"xpack.ingestManager.agentConfigList.descriptionColumnTitle": "説明",
|
||||
"xpack.ingestManager.agentConfigList.loadingAgentConfigsMessage": "エージェント構成を読み込み中...",
|
||||
"xpack.ingestManager.agentConfigList.nameColumnTitle": "名前",
|
||||
|
@ -8313,7 +8310,6 @@
|
|||
"xpack.ingestManager.configDetails.configDetailsTitle": "構成「{id}」",
|
||||
"xpack.ingestManager.configDetails.configNotFoundErrorTitle": "構成「{id}」が見つかりません",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.actionsColumnTitle": "アクション",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.copyActionTitle": "データソースをコピー",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.deleteActionTitle": "データソースを削除",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.descriptionColumnTitle": "説明",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.editActionTitle": "データソースを編集",
|
||||
|
@ -8321,7 +8317,6 @@
|
|||
"xpack.ingestManager.configDetails.datasourcesTable.namespaceColumnTitle": "名前空間",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.packageNameColumnTitle": "パッケージ",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.streamsCountColumnTitle": "ストリーム",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.viewActionTitle": "データソースを表示",
|
||||
"xpack.ingestManager.configDetails.subTabs.datasouces": "データソース",
|
||||
"xpack.ingestManager.configDetails.subTabs.settings": "設定",
|
||||
"xpack.ingestManager.configDetails.subTabs.yamlFile": "YAML ファイル",
|
||||
|
|
|
@ -8228,11 +8228,8 @@
|
|||
"xpack.ingestManager.agentConfigList.addButton": "创建代理配置",
|
||||
"xpack.ingestManager.agentConfigList.agentsColumnTitle": "代理",
|
||||
"xpack.ingestManager.agentConfigList.clearFiltersLinkText": "清除筛选",
|
||||
"xpack.ingestManager.agentConfigList.copyConfigActionText": "复制配置",
|
||||
"xpack.ingestManager.agentConfigList.createDatasourceActionText": "创建数据源",
|
||||
"xpack.ingestManager.agentConfigList.datasourcesCountColumnTitle": "数据源",
|
||||
"xpack.ingestManager.agentConfigList.deleteButton": "删除 {count, plural, one {# 个代理配置} other {# 个代理配置}}",
|
||||
"xpack.ingestManager.agentConfigList.deleteConfigActionText": "删除配置",
|
||||
"xpack.ingestManager.agentConfigList.descriptionColumnTitle": "描述",
|
||||
"xpack.ingestManager.agentConfigList.loadingAgentConfigsMessage": "正在加载代理配置……",
|
||||
"xpack.ingestManager.agentConfigList.nameColumnTitle": "名称",
|
||||
|
@ -8316,7 +8313,6 @@
|
|||
"xpack.ingestManager.configDetails.configDetailsTitle": "配置“{id}”",
|
||||
"xpack.ingestManager.configDetails.configNotFoundErrorTitle": "未找到配置“{id}”",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.actionsColumnTitle": "操作",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.copyActionTitle": "复制数据源",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.deleteActionTitle": "删除数据源",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.descriptionColumnTitle": "描述",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.editActionTitle": "编辑数据源",
|
||||
|
@ -8324,7 +8320,6 @@
|
|||
"xpack.ingestManager.configDetails.datasourcesTable.namespaceColumnTitle": "命名空间",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.packageNameColumnTitle": "软件包",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.streamsCountColumnTitle": "流计数",
|
||||
"xpack.ingestManager.configDetails.datasourcesTable.viewActionTitle": "查看数据源",
|
||||
"xpack.ingestManager.configDetails.subTabs.datasouces": "数据源",
|
||||
"xpack.ingestManager.configDetails.subTabs.settings": "设置",
|
||||
"xpack.ingestManager.configDetails.subTabs.yamlFile": "YAML 文件",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue