mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Enterprise Search] Allow user to customize name and description of connector indices (#141151)
This commit is contained in:
parent
b4467665cb
commit
348b7158f9
24 changed files with 697 additions and 168 deletions
|
@ -41,6 +41,7 @@ export interface IngestPipelineParams {
|
|||
export interface Connector {
|
||||
api_key_id: string | null;
|
||||
configuration: ConnectorConfiguration;
|
||||
description: string | null;
|
||||
error: string | null;
|
||||
id: string;
|
||||
index_name: string;
|
||||
|
|
|
@ -27,6 +27,7 @@ export const indices: ElasticsearchIndexWithIngestion[] = [
|
|||
connector: {
|
||||
api_key_id: null,
|
||||
configuration: { foo: { label: 'bar', value: 'barbar' } },
|
||||
description: null,
|
||||
error: null,
|
||||
id: '2',
|
||||
index_name: 'connector',
|
||||
|
@ -76,6 +77,7 @@ export const indices: ElasticsearchIndexWithIngestion[] = [
|
|||
connector: {
|
||||
api_key_id: null,
|
||||
configuration: { foo: { label: 'bar', value: 'barbar' } },
|
||||
description: null,
|
||||
error: null,
|
||||
id: '4',
|
||||
index_name: 'connector-crawler',
|
||||
|
|
|
@ -36,6 +36,7 @@ export const connectorIndex: ConnectorViewIndex = {
|
|||
connector: {
|
||||
api_key_id: null,
|
||||
configuration: { foo: { label: 'bar', value: 'barbar' } },
|
||||
description: null,
|
||||
error: null,
|
||||
id: '2',
|
||||
index_name: 'connector',
|
||||
|
@ -91,6 +92,7 @@ export const connectorCrawlerIndex: CrawlerViewIndex = {
|
|||
connector: {
|
||||
api_key_id: null,
|
||||
configuration: { foo: { label: 'bar', value: 'barbar' } },
|
||||
description: null,
|
||||
error: null,
|
||||
id: '4',
|
||||
index_name: 'connector-crawler',
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Connector } from '../../../../../common/types/connectors';
|
||||
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
|
||||
export type PutConnectorNameAndDescriptionArgs = Partial<
|
||||
Pick<Connector, 'name' | 'description'>
|
||||
> & {
|
||||
connectorId: string;
|
||||
indexName: string;
|
||||
};
|
||||
|
||||
export type PutConnectorNameAndDescriptionResponse = Partial<
|
||||
Pick<Connector, 'name' | 'description'>
|
||||
> & {
|
||||
indexName: string;
|
||||
};
|
||||
|
||||
export const putConnectorNameAndDescription = async ({
|
||||
connectorId,
|
||||
description,
|
||||
indexName,
|
||||
name,
|
||||
}: PutConnectorNameAndDescriptionArgs) => {
|
||||
const route = `/internal/enterprise_search/connectors/${connectorId}/name_and_description`;
|
||||
|
||||
await HttpLogic.values.http.put(route, {
|
||||
body: JSON.stringify({ description, name }),
|
||||
});
|
||||
return { description, indexName, name };
|
||||
};
|
||||
|
||||
export const ConnectorNameAndDescriptionApiLogic = createApiLogic(
|
||||
['content', 'connector_name_and_description_api_logic'],
|
||||
putConnectorNameAndDescription
|
||||
);
|
|
@ -15,13 +15,7 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { OverviewLogic } from './overview.logic';
|
||||
|
||||
interface TotalStatsProps {
|
||||
additionalItems?: EuiStatProps[];
|
||||
ingestionType: string;
|
||||
lastUpdated?: string;
|
||||
}
|
||||
|
||||
export const TotalStats: React.FC<TotalStatsProps> = ({ ingestionType, additionalItems = [] }) => {
|
||||
export const ApiTotalStats: React.FC = () => {
|
||||
const { indexData, isError, isLoading } = useValues(OverviewLogic);
|
||||
const documentCount = indexData?.count ?? 0;
|
||||
const hideStats = isLoading || isError;
|
||||
|
@ -35,7 +29,12 @@ export const TotalStats: React.FC<TotalStatsProps> = ({ ingestionType, additiona
|
|||
}
|
||||
),
|
||||
isLoading: hideStats,
|
||||
title: ingestionType,
|
||||
title: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndex.totalStats.apiIngestionMethodLabel',
|
||||
{
|
||||
defaultMessage: 'API',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
description: i18n.translate(
|
||||
|
@ -47,7 +46,6 @@ export const TotalStats: React.FC<TotalStatsProps> = ({ ingestionType, additiona
|
|||
isLoading: hideStats,
|
||||
title: documentCount,
|
||||
},
|
||||
...additionalItems,
|
||||
];
|
||||
|
||||
return (
|
|
@ -43,6 +43,7 @@ import { SearchIndexTabId } from '../search_index';
|
|||
|
||||
import { ApiKeyConfig } from './api_key_configuration';
|
||||
import { ConnectorConfigurationConfig } from './connector_configuration_config';
|
||||
import { ConnectorNameAndDescription } from './connector_name_and_description/connector_name_and_description';
|
||||
import { NativeConnectorConfiguration } from './native_connector_configuration/native_connector_configuration';
|
||||
|
||||
export const ConnectorConfiguration: React.FC = () => {
|
||||
|
@ -84,6 +85,17 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
),
|
||||
titleSize: 'xs',
|
||||
},
|
||||
{
|
||||
children: <ConnectorNameAndDescription />,
|
||||
status: indexData.connector.description ? 'complete' : 'incomplete',
|
||||
title: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.steps.nameAndDescriptionTitle',
|
||||
{
|
||||
defaultMessage: 'Name and description',
|
||||
}
|
||||
),
|
||||
titleSize: 'xs',
|
||||
},
|
||||
{
|
||||
children: (
|
||||
<>
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiDescriptionList,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { NAME_LABEL, DESCRIPTION_LABEL, EDIT_BUTTON_LABEL } from '../../../../../shared/constants';
|
||||
|
||||
import { isConnectorIndex } from '../../../../utils/indices';
|
||||
import { IndexViewLogic } from '../../index_view_logic';
|
||||
|
||||
import { ConnectorNameAndDescriptionForm } from './connector_name_and_description_form';
|
||||
import { ConnectorNameAndDescriptionLogic } from './connector_name_and_description_logic';
|
||||
|
||||
export const ConnectorNameAndDescription: React.FC = () => {
|
||||
const { index: indexData } = useValues(IndexViewLogic);
|
||||
const {
|
||||
isEditing,
|
||||
nameAndDescription: { name, description },
|
||||
} = useValues(ConnectorNameAndDescriptionLogic);
|
||||
const { setIsEditing } = useActions(ConnectorNameAndDescriptionLogic);
|
||||
|
||||
if (!isConnectorIndex(indexData)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionForm.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'By naming and describing this connector your colleagues and wider team will know what this connector is meant for.',
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{isEditing ? (
|
||||
<ConnectorNameAndDescriptionForm />
|
||||
) : (
|
||||
<>
|
||||
<EuiDescriptionList
|
||||
listItems={[
|
||||
{
|
||||
description: name ?? '--',
|
||||
title: NAME_LABEL,
|
||||
},
|
||||
{
|
||||
description: description ?? '--',
|
||||
title: DESCRIPTION_LABEL,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={() => setIsEditing(!isEditing)}>{EDIT_BUTTON_LABEL}</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiTextArea,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { Status } from '../../../../../../../common/types/api';
|
||||
import {
|
||||
NAME_LABEL,
|
||||
DESCRIPTION_LABEL,
|
||||
SAVE_BUTTON_LABEL,
|
||||
CANCEL_BUTTON_LABEL,
|
||||
} from '../../../../../shared/constants';
|
||||
import { ConnectorNameAndDescriptionApiLogic } from '../../../../api/connector/update_connector_name_and_description_api_logic';
|
||||
import { isConnectorIndex } from '../../../../utils/indices';
|
||||
import { IndexViewLogic } from '../../index_view_logic';
|
||||
|
||||
import { ConnectorNameAndDescriptionLogic } from './connector_name_and_description_logic';
|
||||
|
||||
export const ConnectorNameAndDescriptionForm: React.FC = () => {
|
||||
const { index: indexData } = useValues(IndexViewLogic);
|
||||
const { status } = useValues(ConnectorNameAndDescriptionApiLogic);
|
||||
const {
|
||||
localNameAndDescription: { name, description },
|
||||
} = useValues(ConnectorNameAndDescriptionLogic);
|
||||
const { saveNameAndDescription, setIsEditing, updateLocalNameAndDescription } = useActions(
|
||||
ConnectorNameAndDescriptionLogic
|
||||
);
|
||||
|
||||
if (!isConnectorIndex(indexData)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiForm
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
saveNameAndDescription();
|
||||
}}
|
||||
component="form"
|
||||
>
|
||||
<EuiFormRow label={NAME_LABEL}>
|
||||
<EuiFieldText
|
||||
required
|
||||
value={name ?? ''}
|
||||
onChange={(event) => {
|
||||
updateLocalNameAndDescription({ name: event.target.value });
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow label={DESCRIPTION_LABEL}>
|
||||
<EuiTextArea
|
||||
placeholder={'Optional'}
|
||||
value={description || ''}
|
||||
onChange={(event) => {
|
||||
updateLocalNameAndDescription({ description: event.target.value });
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton type="submit" isLoading={status === Status.LOADING}>
|
||||
{SAVE_BUTTON_LABEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
isDisabled={status === Status.LOADING}
|
||||
onClick={() => {
|
||||
setIsEditing(false);
|
||||
}}
|
||||
>
|
||||
{CANCEL_BUTTON_LABEL}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { Connector } from '../../../../../../../common/types/connectors';
|
||||
import { Actions } from '../../../../../shared/api_logic/create_api_logic';
|
||||
import {
|
||||
flashAPIErrors,
|
||||
flashSuccessToast,
|
||||
clearFlashMessages,
|
||||
} from '../../../../../shared/flash_messages';
|
||||
import {
|
||||
ConnectorNameAndDescriptionApiLogic,
|
||||
PutConnectorNameAndDescriptionArgs,
|
||||
PutConnectorNameAndDescriptionResponse,
|
||||
} from '../../../../api/connector/update_connector_name_and_description_api_logic';
|
||||
import {
|
||||
FetchIndexApiLogic,
|
||||
FetchIndexApiParams,
|
||||
FetchIndexApiResponse,
|
||||
} from '../../../../api/index/fetch_index_api_logic';
|
||||
import { isConnectorIndex } from '../../../../utils/indices';
|
||||
|
||||
type NameAndDescription = Partial<Pick<Connector, 'name' | 'description'>>;
|
||||
|
||||
type ConnectorNameAndDescriptionActions = Pick<
|
||||
Actions<PutConnectorNameAndDescriptionArgs, PutConnectorNameAndDescriptionResponse>,
|
||||
'apiError' | 'apiSuccess' | 'makeRequest'
|
||||
> & {
|
||||
fetchIndexApiSuccess: Actions<FetchIndexApiParams, FetchIndexApiResponse>['apiSuccess'];
|
||||
saveNameAndDescription: () => void;
|
||||
setIsEditing(isEditing: boolean): { isEditing: boolean };
|
||||
setLocalNameAndDescription(nameAndDescription: NameAndDescription): NameAndDescription;
|
||||
setNameAndDescription(nameAndDescription: NameAndDescription): NameAndDescription;
|
||||
updateLocalNameAndDescription(nameAndDescription: NameAndDescription): NameAndDescription;
|
||||
};
|
||||
|
||||
interface ConnectorNameAndDescriptionValues {
|
||||
index: FetchIndexApiResponse;
|
||||
isEditing: boolean;
|
||||
localNameAndDescription: NameAndDescription;
|
||||
nameAndDescription: NameAndDescription;
|
||||
}
|
||||
|
||||
export const ConnectorNameAndDescriptionLogic = kea<
|
||||
MakeLogicType<ConnectorNameAndDescriptionValues, ConnectorNameAndDescriptionActions>
|
||||
>({
|
||||
actions: {
|
||||
saveNameAndDescription: true,
|
||||
setIsEditing: (isEditing: boolean) => ({
|
||||
isEditing,
|
||||
}),
|
||||
setLocalNameAndDescription: (nameAndDescription) => nameAndDescription,
|
||||
setNameAndDescription: (nameAndDescription) => nameAndDescription,
|
||||
updateLocalNameAndDescription: (nameAndDescription) => nameAndDescription,
|
||||
},
|
||||
connect: {
|
||||
actions: [
|
||||
ConnectorNameAndDescriptionApiLogic,
|
||||
['apiError', 'apiSuccess', 'makeRequest'],
|
||||
FetchIndexApiLogic,
|
||||
['apiSuccess as fetchIndexApiSuccess'],
|
||||
],
|
||||
values: [FetchIndexApiLogic, ['data as index']],
|
||||
},
|
||||
events: ({ actions, values }) => ({
|
||||
afterMount: () =>
|
||||
actions.setNameAndDescription(isConnectorIndex(values.index) ? values.index.connector : {}),
|
||||
}),
|
||||
listeners: ({ actions, values }) => ({
|
||||
apiError: (error) => flashAPIErrors(error),
|
||||
apiSuccess: ({ indexName }) => {
|
||||
flashSuccessToast(
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.configuration.successToast.title',
|
||||
{ defaultMessage: 'Configuration successfully updated' }
|
||||
)
|
||||
);
|
||||
FetchIndexApiLogic.actions.makeRequest({ indexName });
|
||||
},
|
||||
fetchIndexApiSuccess: (index) => {
|
||||
if (!values.isEditing && isConnectorIndex(index)) {
|
||||
actions.setNameAndDescription(index.connector);
|
||||
}
|
||||
},
|
||||
makeRequest: () => clearFlashMessages(),
|
||||
saveNameAndDescription: () => {
|
||||
if (isConnectorIndex(values.index)) {
|
||||
actions.makeRequest({
|
||||
connectorId: values.index.connector.id,
|
||||
indexName: values.index.connector.index_name,
|
||||
...values.localNameAndDescription,
|
||||
});
|
||||
}
|
||||
},
|
||||
setIsEditing: (isEditing) => {
|
||||
if (isEditing) {
|
||||
actions.setLocalNameAndDescription(values.nameAndDescription);
|
||||
}
|
||||
},
|
||||
}),
|
||||
path: ['enterprise_search', 'content', 'connector_name_and_description'],
|
||||
reducers: () => ({
|
||||
isEditing: [
|
||||
false,
|
||||
{
|
||||
apiSuccess: () => false,
|
||||
setIsEditing: (_, { isEditing }) => isEditing,
|
||||
},
|
||||
],
|
||||
localNameAndDescription: [
|
||||
{},
|
||||
{
|
||||
setLocalNameAndDescription: (_, nameAndDescription) => nameAndDescription,
|
||||
updateLocalNameAndDescription: (localNameAndDescription, nameAndDescription) => ({
|
||||
...localNameAndDescription,
|
||||
...nameAndDescription,
|
||||
}),
|
||||
},
|
||||
],
|
||||
nameAndDescription: [
|
||||
{},
|
||||
{
|
||||
apiSuccess: (_, { description, name }) => ({ description, name }),
|
||||
setNameAndDescription: (_, nameAndDescription) => nameAndDescription,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
|
@ -28,8 +28,6 @@ import {
|
|||
import { IndexViewLogic } from '../index_view_logic';
|
||||
import { SearchIndexTabId } from '../search_index';
|
||||
|
||||
import { NATIVE_CONNECTORS } from './constants';
|
||||
|
||||
const StatusPanel: React.FC<{ ingestionStatus: IngestionStatus }> = ({ ingestionStatus }) => (
|
||||
<EuiPanel color={ingestionStatusToColor(ingestionStatus)} hasShadow={false} paddingSize="l">
|
||||
<EuiStat
|
||||
|
@ -47,36 +45,15 @@ export const ConnectorOverviewPanels: React.FC = () => {
|
|||
return isConnectorIndex(index) ? (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiPanel color="subdued" hasShadow={false} paddingSize="l">
|
||||
<EuiPanel color="primary" hasShadow={false} paddingSize="l">
|
||||
<EuiStat
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.connector.connectorNamePanel.title',
|
||||
'xpack.enterpriseSearch.content.searchIndex.totalStats.documentCountCardLabel',
|
||||
{
|
||||
defaultMessage: 'Name',
|
||||
defaultMessage: 'Document count',
|
||||
}
|
||||
)}
|
||||
title={index.connector.name}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiPanel color="subdued" hasShadow={false} paddingSize="l">
|
||||
<EuiStat
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.connector.connectorTypePanel.title',
|
||||
{
|
||||
defaultMessage: 'Connector type',
|
||||
}
|
||||
)}
|
||||
title={
|
||||
NATIVE_CONNECTORS.find(
|
||||
(connector) => connector.serviceType === index.connector.service_type
|
||||
)?.name ??
|
||||
index.connector.service_type ??
|
||||
i18n.translate('xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label', {
|
||||
defaultMessage: 'Unknown',
|
||||
})
|
||||
}
|
||||
title={index.count}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -1,83 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiText,
|
||||
EuiTextArea,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { isConnectorIndex } from '../../../../utils/indices';
|
||||
import { IndexViewLogic } from '../../index_view_logic';
|
||||
import { NativeConnector } from '../types';
|
||||
|
||||
interface ConnectorNameAndDescriptionProps {
|
||||
nativeConnector: NativeConnector;
|
||||
}
|
||||
|
||||
export const ConnectorNameAndDescription: React.FC<ConnectorNameAndDescriptionProps> = ({
|
||||
nativeConnector,
|
||||
}) => {
|
||||
const { index: indexData } = useValues(IndexViewLogic);
|
||||
const { name } = nativeConnector;
|
||||
return (
|
||||
<EuiForm component="form">
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionForm.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'By naming and describing this connector your colleagues and wider team will know what this connector is meant for.',
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow label="Name">
|
||||
<EuiFieldText
|
||||
value={isConnectorIndex(indexData) ? indexData.connector.name : ''}
|
||||
placeholder={`${name} connector`}
|
||||
disabled
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow label="Description">
|
||||
<EuiTextArea placeholder={'Optional'} disabled />
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton disabled type="submit">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionForm.submitButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Save name and description',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiForm>
|
||||
);
|
||||
};
|
|
@ -28,6 +28,7 @@ import { NATIVE_CONNECTOR_ICONS } from '../../../../../../assets/source_icons/na
|
|||
import { hasConfiguredConfiguration } from '../../../../utils/has_configured_configuration';
|
||||
import { isConnectorIndex } from '../../../../utils/indices';
|
||||
import { IndexViewLogic } from '../../index_view_logic';
|
||||
import { ConnectorNameAndDescription } from '../connector_name_and_description/connector_name_and_description';
|
||||
import { NATIVE_CONNECTORS } from '../constants';
|
||||
|
||||
import { NativeConnectorAdvancedConfiguration } from './native_connector_advanced_configuration';
|
||||
|
@ -49,10 +50,11 @@ export const NativeConnectorConfiguration: React.FC = () => {
|
|||
return <></>;
|
||||
}
|
||||
|
||||
const hasDescription = !!indexData.connector.description;
|
||||
const hasConfigured = hasConfiguredConfiguration(indexData.connector.configuration);
|
||||
const hasConfiguredAdvanced =
|
||||
indexData.connector.last_synced || indexData.connector.scheduling.enabled;
|
||||
const hasResearched = hasConfigured || hasConfiguredAdvanced;
|
||||
const hasResearched = hasDescription || hasConfigured || hasConfiguredAdvanced;
|
||||
|
||||
const icon = NATIVE_CONNECTOR_ICONS[nativeConnector.serviceType];
|
||||
return (
|
||||
|
@ -87,18 +89,17 @@ export const NativeConnectorConfiguration: React.FC = () => {
|
|||
),
|
||||
titleSize: 'xs',
|
||||
},
|
||||
/* Commenting this out for a future PR to implement fully */
|
||||
// {
|
||||
// children: <ConnectorNameAndDescription nativeConnector={nativeConnector} />,
|
||||
// status: hasName ? 'complete' : 'incomplete',
|
||||
// title: i18n.translate(
|
||||
// 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.nameAndDescriptionTitle',
|
||||
// {
|
||||
// defaultMessage: 'Name and description',
|
||||
// }
|
||||
// ),
|
||||
// titleSize: 'xs',
|
||||
// },
|
||||
{
|
||||
children: <ConnectorNameAndDescription />,
|
||||
status: hasDescription ? 'complete' : 'incomplete',
|
||||
title: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.nameAndDescriptionTitle',
|
||||
{
|
||||
defaultMessage: 'Name and description',
|
||||
}
|
||||
),
|
||||
titleSize: 'xs',
|
||||
},
|
||||
{
|
||||
children: (
|
||||
<NativeConnectorConfigurationConfig nativeConnector={nativeConnector} />
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiStatProps,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiStat,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { isConnectorIndex } from '../../utils/indices';
|
||||
|
||||
import { ConnectorOverviewPanels } from './connector/connector_overview_panels';
|
||||
import { NATIVE_CONNECTORS } from './connector/constants';
|
||||
import { NameAndDescriptionStats } from './name_and_description_stats';
|
||||
import { OverviewLogic } from './overview.logic';
|
||||
|
||||
export const ConnectorTotalStats: React.FC = () => {
|
||||
const { indexData, isError, isLoading } = useValues(OverviewLogic);
|
||||
const hideStats = isLoading || isError;
|
||||
|
||||
if (!isConnectorIndex(indexData)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const stats: EuiStatProps[] = [
|
||||
{
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndex.totalStats.ingestionTypeCardLabel',
|
||||
{
|
||||
defaultMessage: 'Ingestion type',
|
||||
}
|
||||
),
|
||||
isLoading: hideStats,
|
||||
title: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndex.totalStats.connectorIngestionMethodLabel',
|
||||
{
|
||||
defaultMessage: 'Connector',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
description: i18n.translate('xpack.enterpriseSearch.connector.connectorTypePanel.title', {
|
||||
defaultMessage: 'Connector type',
|
||||
}),
|
||||
title:
|
||||
NATIVE_CONNECTORS.find(
|
||||
(connector) => connector.serviceType === indexData.connector.service_type
|
||||
)?.name ??
|
||||
indexData.connector.service_type ??
|
||||
i18n.translate('xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label', {
|
||||
defaultMessage: 'Unknown',
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndex.totalStats.languageLabel',
|
||||
{
|
||||
defaultMessage: 'Language analyzer',
|
||||
}
|
||||
),
|
||||
isLoading: hideStats,
|
||||
title:
|
||||
indexData.connector.language ??
|
||||
i18n.translate('xpack.enterpriseSearch.content.searchIndex.totalStats.noneLabel', {
|
||||
defaultMessage: 'None',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<NameAndDescriptionStats />
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup direction="row">
|
||||
{stats.map((item, index) => (
|
||||
<EuiFlexItem key={index}>
|
||||
<EuiPanel color="primary" hasShadow={false} paddingSize="l">
|
||||
<EuiStat {...item} />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<ConnectorOverviewPanels />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -9,15 +9,34 @@ import React from 'react';
|
|||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { EuiStatProps, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { CrawlerLogic } from './crawler/crawler_logic';
|
||||
import { TotalStats } from './total_stats';
|
||||
import { OverviewLogic } from './overview.logic';
|
||||
|
||||
export const CrawlerTotalStats: React.FC = () => {
|
||||
const { domains, dataLoading } = useValues(CrawlerLogic);
|
||||
const { indexData, isError, isLoading } = useValues(OverviewLogic);
|
||||
const documentCount = indexData?.count ?? 0;
|
||||
const hideStats = isLoading || isError;
|
||||
|
||||
const additionalItems = [
|
||||
const stats: EuiStatProps[] = [
|
||||
{
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndex.totalStats.ingestionTypeCardLabel',
|
||||
{
|
||||
defaultMessage: 'Ingestion type',
|
||||
}
|
||||
),
|
||||
isLoading: hideStats,
|
||||
title: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndex.totalStats.crawlerIngestionMethodLabel',
|
||||
{
|
||||
defaultMessage: 'Crawler',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndex.totalStats.domainCountCardLabel',
|
||||
|
@ -25,20 +44,30 @@ export const CrawlerTotalStats: React.FC = () => {
|
|||
defaultMessage: 'Domain count',
|
||||
}
|
||||
),
|
||||
isLoading: dataLoading,
|
||||
isLoading: dataLoading || hideStats,
|
||||
title: domains.length,
|
||||
},
|
||||
{
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndex.totalStats.documentCountCardLabel',
|
||||
{
|
||||
defaultMessage: 'Document count',
|
||||
}
|
||||
),
|
||||
isLoading: hideStats,
|
||||
title: documentCount,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<TotalStats
|
||||
ingestionType={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndex.totalStats.crawlerIngestionMethodLabel',
|
||||
{
|
||||
defaultMessage: 'Crawler',
|
||||
}
|
||||
)}
|
||||
additionalItems={additionalItems}
|
||||
/>
|
||||
<EuiFlexGroup direction="row">
|
||||
{stats.map((item, index) => (
|
||||
<EuiFlexItem key={index}>
|
||||
<EuiPanel color={index === 0 ? 'primary' : 'subdued'} hasShadow={false} paddingSize="l">
|
||||
<EuiStat {...item} />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat, EuiStatProps, EuiText } from '@elastic/eui';
|
||||
|
||||
import { DESCRIPTION_LABEL, NAME_LABEL } from '../../../shared/constants';
|
||||
import { generateEncodedPath } from '../../../shared/encode_path_params';
|
||||
import { EuiLinkTo } from '../../../shared/react_router_helpers';
|
||||
import { SEARCH_INDEX_TAB_PATH } from '../../routes';
|
||||
import { isConnectorIndex } from '../../utils/indices';
|
||||
|
||||
import { IndexNameLogic } from './index_name_logic';
|
||||
import { OverviewLogic } from './overview.logic';
|
||||
import { SearchIndexTabId } from './search_index';
|
||||
|
||||
const EditDescription: React.FC<{ label: string; indexName: string }> = ({ label, indexName }) => (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>{label}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLinkTo
|
||||
to={generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
|
||||
indexName,
|
||||
tabId: SearchIndexTabId.CONFIGURATION,
|
||||
})}
|
||||
>
|
||||
Edit
|
||||
</EuiLinkTo>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
export const NameAndDescriptionStats: React.FC = () => {
|
||||
const { indexName } = useValues(IndexNameLogic);
|
||||
const { indexData, isError, isLoading } = useValues(OverviewLogic);
|
||||
const hideStats = isLoading || isError;
|
||||
|
||||
if (!isConnectorIndex(indexData)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const stats: EuiStatProps[] = [
|
||||
{
|
||||
description: <EditDescription label={NAME_LABEL} indexName={indexName} />,
|
||||
isLoading: hideStats,
|
||||
title: indexData.connector.name,
|
||||
},
|
||||
{
|
||||
description: <EditDescription label={DESCRIPTION_LABEL} indexName={indexName} />,
|
||||
isLoading: hideStats,
|
||||
title: <EuiText size="s">{indexData.connector.description || ''}</EuiText>,
|
||||
titleElement: 'p',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="row">
|
||||
{stats.map((item, index) => (
|
||||
<EuiFlexItem key={index}>
|
||||
<EuiPanel color={'subdued'} hasShadow={false} paddingSize="l">
|
||||
<EuiStat {...item} />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -15,14 +15,14 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { isApiIndex, isConnectorIndex, isCrawlerIndex } from '../../utils/indices';
|
||||
|
||||
import { ConnectorOverviewPanels } from './connector/connector_overview_panels';
|
||||
import { ApiTotalStats } from './api_total_stats';
|
||||
import { ConnectorTotalStats } from './connector_total_stats';
|
||||
import { CrawlDetailsFlyout } from './crawler/crawl_details_flyout/crawl_details_flyout';
|
||||
import { CrawlRequestsPanel } from './crawler/crawl_requests_panel/crawl_requests_panel';
|
||||
import { CrawlerTotalStats } from './crawler_total_stats';
|
||||
import { GenerateApiKeyPanel } from './generate_api_key_panel';
|
||||
import { OverviewLogic } from './overview.logic';
|
||||
import { SyncJobs } from './sync_jobs';
|
||||
import { TotalStats } from './total_stats';
|
||||
|
||||
export const SearchIndexOverview: React.FC = () => {
|
||||
const { indexData } = useValues(OverviewLogic);
|
||||
|
@ -50,24 +50,10 @@ export const SearchIndexOverview: React.FC = () => {
|
|||
)}
|
||||
{isCrawlerIndex(indexData) ? (
|
||||
<CrawlerTotalStats />
|
||||
) : isConnectorIndex(indexData) ? (
|
||||
<ConnectorTotalStats />
|
||||
) : (
|
||||
<TotalStats
|
||||
ingestionType={
|
||||
isConnectorIndex(indexData)
|
||||
? i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndex.totalStats.connectorIngestionMethodLabel',
|
||||
{
|
||||
defaultMessage: 'Connector',
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndex.totalStats.apiIngestionMethodLabel',
|
||||
{
|
||||
defaultMessage: 'API',
|
||||
}
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ApiTotalStats />
|
||||
)}
|
||||
{isApiIndex(indexData) && (
|
||||
<>
|
||||
|
@ -84,8 +70,6 @@ export const SearchIndexOverview: React.FC = () => {
|
|||
)}
|
||||
{isConnectorIndex(indexData) && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<ConnectorOverviewPanels />
|
||||
<EuiSpacer />
|
||||
<SyncJobs />
|
||||
</>
|
||||
|
|
|
@ -33,3 +33,11 @@ export const TECHNICAL_PREVIEW_LABEL = i18n.translate(
|
|||
defaultMessage: 'Technical Preview', // title case specifically requested
|
||||
}
|
||||
);
|
||||
|
||||
export const NAME_LABEL = i18n.translate('xpack.enterpriseSearch.nameLabel', {
|
||||
defaultMessage: 'Name',
|
||||
});
|
||||
|
||||
export const DESCRIPTION_LABEL = i18n.translate('xpack.enterpriseSearch.descriptionLabel', {
|
||||
defaultMessage: 'Description',
|
||||
});
|
||||
|
|
|
@ -86,6 +86,7 @@ describe('addConnector lib function', () => {
|
|||
document: {
|
||||
api_key_id: null,
|
||||
configuration: {},
|
||||
description: null,
|
||||
error: null,
|
||||
index_name: 'index_name',
|
||||
is_native: false,
|
||||
|
@ -216,6 +217,7 @@ describe('addConnector lib function', () => {
|
|||
document: {
|
||||
api_key_id: null,
|
||||
configuration: {},
|
||||
description: null,
|
||||
error: null,
|
||||
index_name: 'index_name',
|
||||
is_native: true,
|
||||
|
@ -268,6 +270,7 @@ describe('addConnector lib function', () => {
|
|||
document: {
|
||||
api_key_id: null,
|
||||
configuration: {},
|
||||
description: null,
|
||||
error: null,
|
||||
index_name: 'search-index_name',
|
||||
is_native: false,
|
||||
|
|
|
@ -87,6 +87,7 @@ export const addConnector = async (
|
|||
const document: ConnectorDocument = {
|
||||
api_key_id: null,
|
||||
configuration: {},
|
||||
description: null,
|
||||
error: null,
|
||||
index_name: input.index_name,
|
||||
is_native: input.is_native,
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { CONNECTORS_INDEX } from '../..';
|
||||
|
||||
import { Connector, ConnectorDocument } from '../../../common/types/connectors';
|
||||
|
||||
export const updateConnectorNameAndDescription = async (
|
||||
client: IScopedClusterClient,
|
||||
connectorId: string,
|
||||
connectorUpdates: Partial<Pick<Connector, 'name' | 'description'>>
|
||||
) => {
|
||||
const connectorResult = await client.asCurrentUser.get<ConnectorDocument>({
|
||||
id: connectorId,
|
||||
index: CONNECTORS_INDEX,
|
||||
});
|
||||
const connector = connectorResult._source;
|
||||
if (connector) {
|
||||
const result = await client.asCurrentUser.index<ConnectorDocument>({
|
||||
document: { ...connector, ...connectorUpdates },
|
||||
id: connectorId,
|
||||
index: CONNECTORS_INDEX,
|
||||
});
|
||||
await client.asCurrentUser.indices.refresh({ index: CONNECTORS_INDEX });
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(
|
||||
i18n.translate('xpack.enterpriseSearch.server.connectors.serviceType.error', {
|
||||
defaultMessage: 'Could not find document',
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
|
@ -16,6 +16,7 @@ import { addConnector } from '../../lib/connectors/add_connector';
|
|||
import { fetchSyncJobsByConnectorId } from '../../lib/connectors/fetch_sync_jobs';
|
||||
import { startConnectorSync } from '../../lib/connectors/start_sync';
|
||||
import { updateConnectorConfiguration } from '../../lib/connectors/update_connector_configuration';
|
||||
import { updateConnectorNameAndDescription } from '../../lib/connectors/update_connector_name_and_description';
|
||||
import { updateConnectorScheduling } from '../../lib/connectors/update_connector_scheduling';
|
||||
import { updateConnectorServiceType } from '../../lib/connectors/update_connector_service_type';
|
||||
import { updateConnectorStatus } from '../../lib/connectors/update_connector_status';
|
||||
|
@ -244,4 +245,28 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
|
|||
return response.ok({ body: result });
|
||||
})
|
||||
);
|
||||
|
||||
router.put(
|
||||
{
|
||||
path: '/internal/enterprise_search/connectors/{connectorId}/name_and_description',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
connectorId: schema.string(),
|
||||
}),
|
||||
body: schema.object({
|
||||
name: schema.maybe(schema.string()),
|
||||
description: schema.maybe(schema.string()),
|
||||
}),
|
||||
},
|
||||
},
|
||||
elasticsearchErrorHandler(log, async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const { name, description } = request.body;
|
||||
const result = await updateConnectorNameAndDescription(client, request.params.connectorId, {
|
||||
description,
|
||||
name,
|
||||
});
|
||||
return response.ok({ body: result });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11006,7 +11006,6 @@
|
|||
"xpack.enterpriseSearch.appSearch.tokens.search.name": "Clé de recherche publique",
|
||||
"xpack.enterpriseSearch.appSearch.tokens.update": "La clé d'API \"{name}\" a été mise à jour",
|
||||
"xpack.enterpriseSearch.automaticCrawlSchedule.title": "Planification automatisée de l’indexation",
|
||||
"xpack.enterpriseSearch.connector.connectorNamePanel.title": "Nom",
|
||||
"xpack.enterpriseSearch.connector.connectorTypePanel.title": "Type de connecteur",
|
||||
"xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label": "Inconnu",
|
||||
"xpack.enterpriseSearch.connector.ingestionStatus.title": "Statut de l'ingestion",
|
||||
|
|
|
@ -10992,7 +10992,6 @@
|
|||
"xpack.enterpriseSearch.appSearch.tokens.search.name": "公開検索キー",
|
||||
"xpack.enterpriseSearch.appSearch.tokens.update": "APIキー'{name}'が更新されました",
|
||||
"xpack.enterpriseSearch.automaticCrawlSchedule.title": "自動クローリングスケジュール",
|
||||
"xpack.enterpriseSearch.connector.connectorNamePanel.title": "名前",
|
||||
"xpack.enterpriseSearch.connector.connectorTypePanel.title": "コネクタータイプ",
|
||||
"xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label": "不明",
|
||||
"xpack.enterpriseSearch.connector.ingestionStatus.title": "インジェスチョンステータス",
|
||||
|
|
|
@ -11008,7 +11008,6 @@
|
|||
"xpack.enterpriseSearch.appSearch.tokens.search.name": "公有搜索密钥",
|
||||
"xpack.enterpriseSearch.appSearch.tokens.update": "API 密钥“{name}”已更新",
|
||||
"xpack.enterpriseSearch.automaticCrawlSchedule.title": "自动化爬网计划",
|
||||
"xpack.enterpriseSearch.connector.connectorNamePanel.title": "名称",
|
||||
"xpack.enterpriseSearch.connector.connectorTypePanel.title": "连接器类型",
|
||||
"xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label": "未知",
|
||||
"xpack.enterpriseSearch.connector.ingestionStatus.title": "采集状态",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue