mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Search] Add connectors table (#167900)
## Summary
This adds a connectors table to the Serverless Search plugin. Actions
for the table will be added after [this related
PR](https://github.com/elastic/kibana/pull/167804) has merged.
<img width="1318" alt="Screenshot 2023-10-03 at 17 24 22"
src="62383a52
-e7fd-4458-8db9-d6e433f3f94c">
This commit is contained in:
parent
9e304027e5
commit
ad0c38d75d
16 changed files with 510 additions and 249 deletions
|
@ -7,3 +7,4 @@
|
|||
*/
|
||||
|
||||
export * from './is_category_entry';
|
||||
export * from './sync_status_to_text';
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SyncStatus } from '@kbn/search-connectors';
|
||||
import { SyncStatus } from '..';
|
||||
|
||||
import { syncStatusToColor, syncStatusToText } from './sync_status_to_text';
|
||||
|
|
@ -1,44 +1,47 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SyncJobType, SyncStatus } from '@kbn/search-connectors';
|
||||
import { SyncJobType, SyncStatus } from '..';
|
||||
|
||||
export function syncStatusToText(status: SyncStatus): string {
|
||||
switch (status) {
|
||||
case SyncStatus.COMPLETED:
|
||||
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.completed', {
|
||||
return i18n.translate('searchConnectors.syncStatus.completed', {
|
||||
defaultMessage: 'Sync complete',
|
||||
});
|
||||
case SyncStatus.ERROR:
|
||||
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.error', {
|
||||
return i18n.translate('searchConnectors.syncStatus.error', {
|
||||
defaultMessage: 'Sync failure',
|
||||
});
|
||||
case SyncStatus.IN_PROGRESS:
|
||||
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.inProgress', {
|
||||
return i18n.translate('searchConnectors.syncStatus.inProgress', {
|
||||
defaultMessage: 'Sync in progress',
|
||||
});
|
||||
case SyncStatus.CANCELED:
|
||||
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.canceling', {
|
||||
return i18n.translate('searchConnectors.syncStatus.canceling', {
|
||||
defaultMessage: 'Sync canceled',
|
||||
});
|
||||
case SyncStatus.CANCELING:
|
||||
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.canceled', {
|
||||
return i18n.translate('searchConnectors.syncStatus.canceled', {
|
||||
defaultMessage: 'Canceling sync',
|
||||
});
|
||||
case SyncStatus.PENDING:
|
||||
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.pending', {
|
||||
return i18n.translate('searchConnectors.syncStatus.pending', {
|
||||
defaultMessage: 'Sync pending',
|
||||
});
|
||||
case SyncStatus.SUSPENDED:
|
||||
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.suspended', {
|
||||
return i18n.translate('searchConnectors.syncStatus.suspended', {
|
||||
defaultMessage: 'Sync suspended',
|
||||
});
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,17 +57,19 @@ export function syncStatusToColor(status: SyncStatus): string {
|
|||
case SyncStatus.SUSPENDED:
|
||||
case SyncStatus.CANCELING:
|
||||
return 'warning';
|
||||
default:
|
||||
return 'default';
|
||||
}
|
||||
}
|
||||
|
||||
export const syncJobTypeToText = (syncType: SyncJobType): string => {
|
||||
switch (syncType) {
|
||||
case SyncJobType.FULL:
|
||||
return i18n.translate('xpack.enterpriseSearch.content.syncJobType.full', {
|
||||
return i18n.translate('searchConnectors.syncJobType.full', {
|
||||
defaultMessage: 'Full content',
|
||||
});
|
||||
case SyncJobType.INCREMENTAL:
|
||||
return i18n.translate('xpack.enterpriseSearch.content.syncJobType.incremental', {
|
||||
return i18n.translate('searchConnectors.syncJobType.incremental', {
|
||||
defaultMessage: 'Incremental content',
|
||||
});
|
||||
default:
|
|
@ -15,15 +15,12 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { SyncJobType, SyncStatus } from '@kbn/search-connectors';
|
||||
|
||||
import { syncJobTypeToText, syncStatusToColor, syncStatusToText } from '@kbn/search-connectors';
|
||||
|
||||
import { FormattedDateTime } from '../../../../shared/formatted_date_time';
|
||||
import { pageToPagination } from '../../../../shared/pagination/page_to_pagination';
|
||||
|
||||
import { durationToText } from '../../../utils/duration_to_text';
|
||||
import {
|
||||
syncJobTypeToText,
|
||||
syncStatusToColor,
|
||||
syncStatusToText,
|
||||
} from '../../../utils/sync_status_to_text';
|
||||
|
||||
import { IndexViewLogic } from '../index_view_logic';
|
||||
|
||||
|
|
|
@ -54,6 +54,9 @@ export const INVALID_JSON_ERROR: string = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const CONNECTORS_LABEL: string = i18n.translate('xpack.serverlessSearch.connectors', {
|
||||
defaultMessage: 'Connectors',
|
||||
});
|
||||
export const CONNECTOR_LABEL: string = i18n.translate('xpack.serverlessSearch.connector', {
|
||||
defaultMessage: 'Connector',
|
||||
});
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* 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 {
|
||||
Criteria,
|
||||
EuiBadge,
|
||||
EuiBasicTable,
|
||||
EuiBasicTableColumn,
|
||||
EuiEmptyPrompt,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiI18nNumber,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiSearchBar,
|
||||
EuiSelect,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
Connector,
|
||||
ConnectorStatus,
|
||||
SyncStatus,
|
||||
syncStatusToColor,
|
||||
syncStatusToText,
|
||||
} from '@kbn/search-connectors';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { CONNECTORS_LABEL } from '../../../../common/i18n_string';
|
||||
import { useConnectors } from '../../hooks/api/use_connectors';
|
||||
import { useConnectorTypes } from '../../hooks/api/use_connector_types';
|
||||
import { EDIT_CONNECTOR_PATH } from '../connectors_router';
|
||||
|
||||
export const ConnectorsTable: React.FC = () => {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [query, setQuery] = useState<string>('');
|
||||
|
||||
const { data, isError } = useConnectors();
|
||||
const { data: connectorTypes } = useConnectorTypes();
|
||||
|
||||
type Filter = 'service_type' | 'name' | 'last_sync_status' | 'description' | 'service_type';
|
||||
const [filter, setFilter] = useState<Filter>('name');
|
||||
|
||||
useEffect(() => {
|
||||
if (query) {
|
||||
setPageIndex(0);
|
||||
}
|
||||
}, [query, filter]);
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<EuiEmptyPrompt>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.errorFetchingConnectors', {
|
||||
defaultMessage: 'We encountered an error fetching your connectors.',
|
||||
})}
|
||||
</EuiEmptyPrompt>
|
||||
);
|
||||
}
|
||||
|
||||
const connectedLabel = i18n.translate('xpack.serverlessSearch.connectors.connected', {
|
||||
defaultMessage: 'Connected',
|
||||
});
|
||||
|
||||
const configuredLabel = i18n.translate('xpack.serverlessSearch.connectors.configuredLabel', {
|
||||
defaultMessage: 'Configured',
|
||||
});
|
||||
|
||||
const typeLabel = i18n.translate('xpack.serverlessSearch.connectors.typeLabel', {
|
||||
defaultMessage: 'Type',
|
||||
});
|
||||
|
||||
const nameLabel = i18n.translate('xpack.serverlessSearch.connectors.nameLabel', {
|
||||
defaultMessage: 'Name',
|
||||
});
|
||||
|
||||
const syncStatusLabel = i18n.translate('xpack.serverlessSearch.connectors.syncStatusLabel', {
|
||||
defaultMessage: 'Sync status',
|
||||
});
|
||||
|
||||
const filterOptions: Array<{ text: string; value: Filter }> = [
|
||||
{ text: nameLabel, value: 'name' },
|
||||
{ text: typeLabel, value: 'service_type' },
|
||||
{
|
||||
text: i18n.translate('xpack.serverlessSearch.connectors.descriptionLabel', {
|
||||
defaultMessage: 'Description',
|
||||
}),
|
||||
value: 'description',
|
||||
},
|
||||
{ text: syncStatusLabel, value: 'last_sync_status' },
|
||||
];
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<Connector>> = [
|
||||
{
|
||||
field: 'name',
|
||||
name: nameLabel,
|
||||
render: (name: string, connector: Connector) => (
|
||||
<EuiLink href={generatePath(EDIT_CONNECTOR_PATH, { id: connector.id })}>
|
||||
{name || connector.id}
|
||||
</EuiLink>
|
||||
),
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'service_type',
|
||||
name: typeLabel,
|
||||
render: (serviceType: string | null) => {
|
||||
const typeData = (connectorTypes?.connectors || []).find(
|
||||
(connector) => connector.serviceType === (serviceType ?? '')
|
||||
);
|
||||
if (!typeData) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type={typeData.iconPath} aria-label={typeData.name} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">{typeData.name}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
name: connectedLabel,
|
||||
render: (status: ConnectorStatus) =>
|
||||
status === ConnectorStatus.CONNECTED ? (
|
||||
<EuiIcon aria-label={connectedLabel} color="success" type="checkInCircleFilled" />
|
||||
) : (
|
||||
<EuiBadge>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.notConnectedLabel', {
|
||||
defaultMessage: 'Not connected',
|
||||
})}
|
||||
</EuiBadge>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
name: configuredLabel,
|
||||
render: (status: ConnectorStatus) =>
|
||||
[ConnectorStatus.CONNECTED, ConnectorStatus.CONFIGURED].includes(status) ? (
|
||||
<EuiIcon aria-label={configuredLabel} color="success" type="checkInCircleFilled" />
|
||||
) : (
|
||||
<EuiBadge>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.notConfiguredLabel', {
|
||||
defaultMessage: 'Not configured',
|
||||
})}
|
||||
</EuiBadge>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'last_sync_status',
|
||||
name: syncStatusLabel,
|
||||
render: (syncStatus: SyncStatus | null) =>
|
||||
syncStatus ? (
|
||||
<EuiBadge color={syncStatusToColor(syncStatus)}>{syncStatusToText(syncStatus)}</EuiBadge>
|
||||
) : (
|
||||
<EuiBadge>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.notSyncedLabel', {
|
||||
defaultMessage: 'Not synced',
|
||||
})}
|
||||
</EuiBadge>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const items =
|
||||
data?.connectors
|
||||
.filter((connector) =>
|
||||
filter ? `${connector[filter]}`.toLowerCase().includes(query.toLowerCase()) : true
|
||||
)
|
||||
.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize) ?? [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem>
|
||||
<EuiSearchBar onChange={({ queryText }) => setQuery(queryText ?? '')} query={query} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSelect
|
||||
onChange={(e) => setFilter(e.currentTarget.value as Filter)}
|
||||
options={filterOptions}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xl" />
|
||||
<EuiText size="xs">
|
||||
<FormattedMessage
|
||||
id="xpack.serverlessSearch.connectorsTable.summaryLabel"
|
||||
defaultMessage="Showing {items} of {count} {connectors}"
|
||||
values={{
|
||||
connectors: <strong>{CONNECTORS_LABEL}</strong>,
|
||||
items: (
|
||||
<strong>
|
||||
<EuiI18nNumber value={(pageIndex + 1) * pageSize} />-
|
||||
<EuiI18nNumber value={(pageIndex + 2) * pageSize} />
|
||||
</strong>
|
||||
),
|
||||
count: <EuiI18nNumber value={items.length} />,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiHorizontalRule margin="none" style={{ height: 2 }} />
|
||||
<EuiBasicTable
|
||||
columns={columns}
|
||||
items={items}
|
||||
onChange={({ page }: Criteria<Connector>) => {
|
||||
if (page) {
|
||||
const { index, size } = page;
|
||||
setPageIndex(index);
|
||||
setPageSize(size);
|
||||
}
|
||||
}}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalItemCount: data?.connectors.length ?? 0,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -42,7 +42,7 @@ export const EditDescription: React.FC<EditDescriptionProps> = ({
|
|||
|
||||
const { isLoading, isSuccess, mutate } = useMutation({
|
||||
mutationFn: async (inputDescription: string) => {
|
||||
const body = { Description: inputDescription };
|
||||
const body = { description: inputDescription };
|
||||
const result = await http.post(
|
||||
`/internal/serverless_search/connectors/${connectorId}/description`,
|
||||
{
|
||||
|
@ -83,7 +83,7 @@ export const EditDescription: React.FC<EditDescriptionProps> = ({
|
|||
value={newDescription}
|
||||
/>
|
||||
) : (
|
||||
<EuiText>{description}</EuiText>
|
||||
<EuiText size="s">{description}</EuiText>
|
||||
)}
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiIcon,
|
||||
EuiTitle,
|
||||
EuiText,
|
||||
EuiLink,
|
||||
EuiButton,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { PLUGIN_ID } from '../../../../common';
|
||||
import { useConnectorTypes } from '../../hooks/api/use_connector_types';
|
||||
import { useKibanaServices } from '../../hooks/use_kibana';
|
||||
|
||||
export const EmptyConnectorsPrompt: React.FC = () => {
|
||||
const { http } = useKibanaServices();
|
||||
const { data: connectorTypes } = useConnectorTypes();
|
||||
const assetBasePath = http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets`);
|
||||
const connectorsPath = assetBasePath + '/connectors.svg';
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiPanel paddingSize="l" hasShadow={false} hasBorder>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiIcon size="xxl" type={connectorsPath} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.title', {
|
||||
defaultMessage: 'Create a connector',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.description', {
|
||||
defaultMessage:
|
||||
"To set up and deploy a connector you'll be working between the third-party data source, your terminal, and the Kibana UI. The high level process looks like this:",
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel color="subdued">
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="center" alignItems="center" direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon color="primary" size="l" type="documents" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.serverlessSearch.connectorsEmpty.guideOneDescription',
|
||||
{
|
||||
defaultMessage: "Choose a data source you'd like to sync",
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="center" alignItems="center" direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon color="primary" size="l" type={connectorsPath} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.serverlessSearch.connectorsEmpty.guideTwoDescription"
|
||||
defaultMessage="Deploy connector code on your own infrastructure by running from {source}, or using {docker}"
|
||||
values={{
|
||||
source: (
|
||||
<EuiLink href="TODO TODO TODO">
|
||||
{i18n.translate(
|
||||
'xpack.serverlessSearch.connectorsEmpty.sourceLabel',
|
||||
{ defaultMessage: 'source' }
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
docker: (
|
||||
<EuiLink href="TODO TODO TODO">
|
||||
{i18n.translate(
|
||||
'xpack.serverlessSearch.connectorsEmpty.dockerLabel',
|
||||
{ defaultMessage: 'Docker' }
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="center" alignItems="center" direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiIcon color="primary" size="l" type="documents" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiIcon size="m" type="sortRight" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiIcon color="primary" size="l" type={connectorsPath} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiIcon size="m" type="sortRight" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiIcon color="primary" size="l" type="logoElastic" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.serverlessSearch.connectorsEmpty.guideThreeDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Enter access and connection details for your data source and run your first sync',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiButton fill iconType="plusInCircleFilled">
|
||||
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.createConnector', {
|
||||
defaultMessage: 'Create connector',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.availableConnectors', {
|
||||
defaultMessage: 'Available connectors',
|
||||
})}
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
{connectorTypes?.connectors.map((connectorType) => (
|
||||
<EuiFlexItem key={connectorType.name}>
|
||||
<EuiToolTip content={connectorType.name}>
|
||||
<EuiIcon
|
||||
size="l"
|
||||
title={connectorType.name}
|
||||
id={connectorType.serviceType}
|
||||
type={connectorType.iconPath}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -12,40 +12,29 @@ import {
|
|||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiPageTemplate,
|
||||
EuiPanel,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { Connector } from '@kbn/search-connectors';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { LEARN_MORE_LABEL } from '../../../common/i18n_string';
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { useConnectors } from '../hooks/api/use_connectors';
|
||||
import { useKibanaServices } from '../hooks/use_kibana';
|
||||
import { EmptyConnectorsPrompt } from './connectors/empty_connectors_prompt';
|
||||
import { ConnectorsTable } from './connectors/connectors_table';
|
||||
import { CREATE_CONNECTOR_PATH } from './connectors_router';
|
||||
import { useConnectorTypes } from '../hooks/api/use_connector_types';
|
||||
|
||||
export const ConnectorsOverview = () => {
|
||||
const { data } = useConnectors();
|
||||
const {
|
||||
application: { navigateToUrl },
|
||||
http,
|
||||
} = useKibanaServices();
|
||||
|
||||
const assetBasePath = http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets`);
|
||||
const connectorsPath = assetBasePath + '/connectors.svg';
|
||||
|
||||
const { data } = useQuery({
|
||||
queryKey: ['fetchConnectors'],
|
||||
queryFn: () =>
|
||||
http.fetch<{ connectors: Connector[] }>('/internal/serverless_search/connectors'),
|
||||
});
|
||||
|
||||
const { data: connectorTypes } = useConnectorTypes();
|
||||
|
||||
const {
|
||||
data: connector,
|
||||
isLoading,
|
||||
|
@ -61,7 +50,9 @@ export const ConnectorsOverview = () => {
|
|||
});
|
||||
|
||||
useEffect(() => {
|
||||
navigateToUrl(`${CREATE_CONNECTOR_PATH}/${connector?.id}`);
|
||||
if (isSuccess) {
|
||||
navigateToUrl(`${CREATE_CONNECTOR_PATH}/${connector?.id}`);
|
||||
}
|
||||
}, [connector, isSuccess, navigateToUrl]);
|
||||
|
||||
const createConnector = () => mutate();
|
||||
|
@ -131,189 +122,12 @@ export const ConnectorsOverview = () => {
|
|||
</EuiText>
|
||||
</EuiPageTemplate.Header>
|
||||
{(data?.connectors || []).length > 0 ? (
|
||||
<></>
|
||||
<EuiPageTemplate.Section restrictWidth color="subdued">
|
||||
<ConnectorsTable />
|
||||
</EuiPageTemplate.Section>
|
||||
) : (
|
||||
<EuiPageTemplate.Section grow restrictWidth color="subdued">
|
||||
<EuiFlexGroup alignItems="center" direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiPanel paddingSize="l" hasShadow={false} hasBorder>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiIcon size="xxl" type={connectorsPath} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.title', {
|
||||
defaultMessage: 'Create a connector',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.description', {
|
||||
defaultMessage:
|
||||
"To set up and deploy a connector you'll be working between the third-party data source, your terminal, and the Kibana UI. The high level process looks like this:",
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel color="subdued">
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
direction="column"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon color="primary" size="l" type="documents" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.serverlessSearch.connectorsEmpty.guideOneDescription',
|
||||
{
|
||||
defaultMessage: "Choose a data source you'd like to sync",
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
direction="column"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon color="primary" size="l" type={connectorsPath} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.serverlessSearch.connectorsEmpty.guideTwoDescription"
|
||||
defaultMessage="Deploy connector code on your own infrastructure by running from {source}, or using {docker}"
|
||||
values={{
|
||||
source: (
|
||||
<EuiLink href="TODO TODO TODO">
|
||||
{i18n.translate(
|
||||
'xpack.serverlessSearch.connectorsEmpty.sourceLabel',
|
||||
{ defaultMessage: 'source' }
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
docker: (
|
||||
<EuiLink href="TODO TODO TODO">
|
||||
{i18n.translate(
|
||||
'xpack.serverlessSearch.connectorsEmpty.dockerLabel',
|
||||
{ defaultMessage: 'Docker' }
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
direction="column"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiIcon color="primary" size="l" type="documents" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiIcon size="m" type="sortRight" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiIcon color="primary" size="l" type={connectorsPath} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiIcon size="m" type="sortRight" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiIcon color="primary" size="l" type="logoElastic" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.serverlessSearch.connectorsEmpty.guideThreeDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Enter access and connection details for your data source and run your first sync',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiButton
|
||||
isLoading={isLoading}
|
||||
fill
|
||||
iconType="plusInCircleFilled"
|
||||
onClick={() => createConnector()}
|
||||
>
|
||||
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.createConnector', {
|
||||
defaultMessage: 'Create connector',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.availableConnectors', {
|
||||
defaultMessage: 'Available connectors',
|
||||
})}
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
{connectorTypes?.connectors.map((connectorType) => (
|
||||
<EuiFlexItem key={connectorType.name}>
|
||||
<EuiToolTip content={connectorType.name}>
|
||||
<EuiIcon
|
||||
size="l"
|
||||
title={connectorType.name}
|
||||
id={connectorType.serviceType}
|
||||
type={connectorType.iconPath}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiPageTemplate.Section restrictWidth color="subdued">
|
||||
<EmptyConnectorsPrompt />
|
||||
</EuiPageTemplate.Section>
|
||||
)}
|
||||
</EuiPageTemplate>
|
||||
|
|
|
@ -12,7 +12,8 @@ import { ConnectorsOverview } from './connectors_overview';
|
|||
|
||||
export const BASE_CONNECTORS_PATH = 'connectors';
|
||||
export const CREATE_CONNECTOR_SLUG = `create_connector`;
|
||||
export const CREATE_CONNECTOR_PATH = `${BASE_CONNECTORS_PATH}/${CREATE_CONNECTOR_SLUG}`;
|
||||
export const EDIT_CONNECTOR_PATH = `${CREATE_CONNECTOR_SLUG}/:id`;
|
||||
export const CREATE_CONNECTOR_PATH = `${CREATE_CONNECTOR_SLUG}`;
|
||||
|
||||
export const ConnectorsRouter: React.FC = () => {
|
||||
return (
|
||||
|
|
|
@ -11,6 +11,7 @@ import { useKibanaServices } from '../use_kibana';
|
|||
|
||||
export const useConnectorTypes = () => {
|
||||
const { http } = useKibanaServices();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['fetchConnectorTypes'],
|
||||
queryFn: () =>
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Connector } from '@kbn/search-connectors';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useKibanaServices } from '../use_kibana';
|
||||
|
||||
export const useConnectors = (from: number = 0, size: number = 10) => {
|
||||
const { http } = useKibanaServices();
|
||||
return useQuery({
|
||||
queryKey: ['fetchConnectors', from, size],
|
||||
queryFn: () =>
|
||||
http.fetch<{ connectors: Connector[] }>('/internal/serverless_search/connectors', {
|
||||
query: { from, size },
|
||||
}),
|
||||
});
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 { PLUGIN_ID } from '../../../common';
|
||||
import { useKibanaServices } from './use_kibana';
|
||||
|
||||
export const useAssetBasePath = () => {
|
||||
const { http } = useKibanaServices();
|
||||
return http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets`);
|
||||
};
|
|
@ -13818,15 +13818,6 @@
|
|||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.legend": "Sélectionnez le type de tâche de synchronisation à afficher.",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.name": "Type de tâche de synchronisation",
|
||||
"xpack.enterpriseSearch.content.syncJobs.syncDuration.columnTitle": "Durée de synchronisation",
|
||||
"xpack.enterpriseSearch.content.syncJobType.full": "Contenu entier",
|
||||
"xpack.enterpriseSearch.content.syncJobType.incremental": "Contenu progressif",
|
||||
"xpack.enterpriseSearch.content.syncStatus.canceled": "Annulation de la synchronisation",
|
||||
"xpack.enterpriseSearch.content.syncStatus.canceling": "Synchronisation annulée",
|
||||
"xpack.enterpriseSearch.content.syncStatus.completed": "Synchronisation terminée",
|
||||
"xpack.enterpriseSearch.content.syncStatus.error": "Échec de la synchronisation",
|
||||
"xpack.enterpriseSearch.content.syncStatus.inProgress": "Synchronisation en cours",
|
||||
"xpack.enterpriseSearch.content.syncStatus.pending": "Synchronisation en attente",
|
||||
"xpack.enterpriseSearch.content.syncStatus.suspended": "Synchronisation suspendue",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.description": "Vous pouvez ajouter plusieurs domaines au robot d'indexation de cet index. Ajoutez un autre domaine ici et modifiez les points d'entrée et les règles d'indexation à partir de la page \"Gérer\".",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.openButtonLabel": "Ajouter un domaine",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.title": "Ajouter un nouveau domaine",
|
||||
|
|
|
@ -13832,15 +13832,6 @@
|
|||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.legend": "表示する同期ジョブタイプを選択します。",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.name": "同期ジョブタイプ",
|
||||
"xpack.enterpriseSearch.content.syncJobs.syncDuration.columnTitle": "同期時間",
|
||||
"xpack.enterpriseSearch.content.syncJobType.full": "完全なコンテンツ",
|
||||
"xpack.enterpriseSearch.content.syncJobType.incremental": "増分コンテンツ",
|
||||
"xpack.enterpriseSearch.content.syncStatus.canceled": "同期のキャンセル中",
|
||||
"xpack.enterpriseSearch.content.syncStatus.canceling": "同期がキャンセルされました",
|
||||
"xpack.enterpriseSearch.content.syncStatus.completed": "同期完了",
|
||||
"xpack.enterpriseSearch.content.syncStatus.error": "同期失敗",
|
||||
"xpack.enterpriseSearch.content.syncStatus.inProgress": "同期は実行中です",
|
||||
"xpack.enterpriseSearch.content.syncStatus.pending": "同期は保留中です",
|
||||
"xpack.enterpriseSearch.content.syncStatus.suspended": "同期が一時停止されました",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.description": "複数のドメインをこのインデックスのWebクローラーに追加できます。ここで別のドメインを追加して、[管理]ページからエントリポイントとクロールルールを変更します。",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.openButtonLabel": "ドメインを追加",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.title": "新しいドメインを追加",
|
||||
|
|
|
@ -13832,15 +13832,6 @@
|
|||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.legend": "选择要显示的同步作业类型。",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.name": "同步作业类型",
|
||||
"xpack.enterpriseSearch.content.syncJobs.syncDuration.columnTitle": "同步持续时间",
|
||||
"xpack.enterpriseSearch.content.syncJobType.full": "完整内容",
|
||||
"xpack.enterpriseSearch.content.syncJobType.incremental": "增量同步",
|
||||
"xpack.enterpriseSearch.content.syncStatus.canceled": "正在取消同步",
|
||||
"xpack.enterpriseSearch.content.syncStatus.canceling": "同步已取消",
|
||||
"xpack.enterpriseSearch.content.syncStatus.completed": "同步已完成",
|
||||
"xpack.enterpriseSearch.content.syncStatus.error": "同步失败",
|
||||
"xpack.enterpriseSearch.content.syncStatus.inProgress": "同步进行中",
|
||||
"xpack.enterpriseSearch.content.syncStatus.pending": "同步待处理",
|
||||
"xpack.enterpriseSearch.content.syncStatus.suspended": "同步已挂起",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.description": "可以将多个域添加到此索引的网络爬虫。在此添加其他域并从“管理”页面修改入口点和爬网规则。",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.openButtonLabel": "添加域",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.title": "添加新域",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue