[Enterprise Search] Allow user to set authentication for Elastic Web Crawler domains (#140432)

This commit is contained in:
Byron Hulcher 2022-09-15 22:43:04 -04:00 committed by GitHub
parent b4d09651a7
commit 73ba80778e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 655 additions and 49 deletions

View file

@ -57,6 +57,11 @@ export const CRAWLER_DOMAIN_CONFIG_FROM_SERVER: DomainConfigFromServer = {
};
export const CRAWLER_DOMAIN_FROM_SERVER: CrawlerDomainFromServer = {
auth: {
type: 'basic',
username: 'username',
password: 'password',
},
available_deduplication_fields: ['title', 'url'],
crawl_rules: [CRAWL_RULE],
created_on: '1657234422',
@ -84,6 +89,11 @@ export const CRAWLER_DOMAIN_CONFIG: DomainConfig = {
};
export const CRAWLER_DOMAIN: CrawlerDomain = {
auth: {
type: 'basic',
username: 'username',
password: 'password',
},
availableDeduplicationFields: ['title', 'url'],
crawlRules: [CRAWL_RULE],
createdOn: '1657234422',

View file

@ -63,9 +63,23 @@ export enum CrawlType {
Partial = 'partial',
}
export interface BasicCrawlerAuth {
password: string;
type: 'basic';
username: string;
}
export interface RawCrawlerAuth {
header: string;
type: 'raw';
}
export type CrawlerAuth = BasicCrawlerAuth | RawCrawlerAuth | null;
// Server
export interface CrawlerDomainFromServer {
auth: CrawlerAuth;
available_deduplication_fields: string[];
crawl_rules: CrawlRule[];
created_on: string;
@ -150,6 +164,7 @@ export interface DomainConfigFromServer {
// Client
export interface CrawlerDomain {
auth: CrawlerAuth;
availableDeduplicationFields: string[];
crawlRules: CrawlRule[];
createdOn: string;

View file

@ -26,25 +26,30 @@ import {
DomainConfigFromServer,
CrawlerDomainsWithMetaFromServer,
CrawlerDomainsWithMeta,
BasicCrawlerAuth,
CrawlerAuth,
RawCrawlerAuth,
} from './types';
export function crawlerDomainServerToClient(payload: CrawlerDomainFromServer): CrawlerDomain {
const {
id,
name,
sitemaps,
created_on: createdOn,
last_visited_at: lastCrawl,
document_count: documentCount,
auth,
available_deduplication_fields: availableDeduplicationFields,
crawl_rules: crawlRules,
default_crawl_rule: defaultCrawlRule,
entry_points: entryPoints,
created_on: createdOn,
deduplication_enabled: deduplicationEnabled,
deduplication_fields: deduplicationFields,
available_deduplication_fields: availableDeduplicationFields,
default_crawl_rule: defaultCrawlRule,
document_count: documentCount,
entry_points: entryPoints,
id,
last_visited_at: lastCrawl,
name,
sitemaps,
} = payload;
const clientPayload: CrawlerDomain = {
auth,
availableDeduplicationFields,
crawlRules,
createdOn,
@ -235,3 +240,11 @@ export const crawlerDomainsWithMetaServerToClient = ({
domains: results.map(crawlerDomainServerToClient),
meta,
});
export function isBasicCrawlerAuth(auth: CrawlerAuth): auth is BasicCrawlerAuth {
return auth !== null && (auth as BasicCrawlerAuth).type === 'basic';
}
export function isRawCrawlerAuth(auth: CrawlerAuth): auth is RawCrawlerAuth {
return auth !== null && (auth as RawCrawlerAuth).type === 'raw';
}

View file

@ -0,0 +1,5 @@
.authenticationPanel {
.authenticationCheckable {
height: 100%;
}
}

View file

@ -0,0 +1,53 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { DataPanel } from '../../../../shared/data_panel/data_panel';
import { AuthenticationPanelActions } from './authentication_panel_actions';
import { AuthenticationPanelDeleteConfirmationModal } from './authentication_panel_delete_confirmation_modal';
import { AuthenticationPanelEditContent } from './authentication_panel_edit_content';
import { AuthenticationPanelLogic } from './authentication_panel_logic';
import { AuthenticationPanelViewContent } from './authentication_panel_view_content';
import './authentication_panel.scss';
export const AuthenticationPanel: React.FC = () => {
const { isEditing, isModalVisible } = useValues(AuthenticationPanelLogic);
return (
<>
<DataPanel
className="authenticationPanel"
hasBorder
title={
<h2>
{i18n.translate('xpack.enterpriseSearch.crawler.authenticationPanel.title', {
defaultMessage: 'Authentication',
})}
</h2>
}
action={<AuthenticationPanelActions />}
subtitle={
<FormattedMessage
id="xpack.enterpriseSearch.crawler.authenticationPanel.description"
defaultMessage="Setup authentication to enable crawling protected content for this domain."
/>
}
>
{isEditing ? <AuthenticationPanelEditContent /> : <AuthenticationPanelViewContent />}
</DataPanel>
{isModalVisible && <AuthenticationPanelDeleteConfirmationModal />}
</>
);
};

View file

@ -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, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import {
SAVE_BUTTON_LABEL,
CANCEL_BUTTON_LABEL,
DELETE_BUTTON_LABEL,
} from '../../../../shared/constants';
import { CrawlerAuth } from '../../../api/crawler/types';
import { CrawlerDomainDetailLogic } from '../crawler_domain_detail_logic';
import { AuthenticationPanelLogic } from './authentication_panel_logic';
export const AuthenticationPanelActions: React.FC = () => {
const { domain } = useValues(CrawlerDomainDetailLogic);
const currentAuth: CrawlerAuth = domain?.auth ?? null;
const { disableEditing, enableEditing, saveCredentials, setIsModalVisible } =
useActions(AuthenticationPanelLogic);
const { isEditing } = useValues(AuthenticationPanelLogic);
return isEditing ? (
<EuiFlexGroup gutterSize="s">
<EuiFlexItem>
<EuiButtonEmpty
iconType="checkInCircleFilled"
size="s"
color="primary"
onClick={() => saveCredentials()}
>
{SAVE_BUTTON_LABEL}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem>
<EuiButtonEmpty
iconType="crossInACircleFilled"
size="s"
color="danger"
onClick={() => disableEditing()}
>
{CANCEL_BUTTON_LABEL}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
) : currentAuth === null ? (
<EuiButton
color="success"
iconType="plusInCircle"
size="s"
onClick={() => enableEditing(currentAuth)}
>
{i18n.translate(
'xpack.enterpriseSearch.crawler.authenticationPanel.resetToDefaultsButtonLabel',
{
defaultMessage: 'Add credentials',
}
)}
</EuiButton>
) : (
<EuiButtonEmpty
color="primary"
size="s"
onClick={() => {
setIsModalVisible(true);
}}
>
{DELETE_BUTTON_LABEL}
</EuiButtonEmpty>
);
};

View file

@ -0,0 +1,57 @@
/*
* 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 } from 'kea';
import { EuiConfirmModal } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { CANCEL_BUTTON_LABEL } from '../../../../shared/constants';
import { AuthenticationPanelLogic } from './authentication_panel_logic';
export const AuthenticationPanelDeleteConfirmationModal: React.FC = () => {
const { deleteCredentials, setIsModalVisible } = useActions(AuthenticationPanelLogic);
return (
<EuiConfirmModal
title={i18n.translate(
'xpack.enterpriseSearch.crawler.authenticationPanel.deleteConfirmationModal.title',
{
defaultMessage: 'Are you sure you want to delete these settings?',
}
)}
onCancel={(event) => {
event?.preventDefault();
setIsModalVisible(false);
}}
onConfirm={(event) => {
event.preventDefault();
deleteCredentials();
}}
cancelButtonText={CANCEL_BUTTON_LABEL}
confirmButtonText={i18n.translate(
'xpack.enterpriseSearch.crawler.authenticationPanel.deleteConfirmationModal.deleteButtonLabel',
{
defaultMessage: 'Delete',
}
)}
defaultFocusedButton="confirm"
buttonColor="danger"
>
{i18n.translate(
'xpack.enterpriseSearch.crawler.authenticationPanel.deleteConfirmationModal.description',
{
defaultMessage:
'Deleting these settings might prevent the crawler from indexing protected areas of the domain. This can not be undone.',
}
)}
</EuiConfirmModal>
);
};

View file

@ -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 { useActions, useValues } from 'kea';
import {
EuiForm,
EuiFieldText,
EuiFieldPassword,
EuiFormRow,
EuiTitle,
EuiCheckableCard,
EuiFormFieldset,
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import { USERNAME_LABEL, PASSWORD_LABEL, TOKEN_LABEL } from '../../../../shared/constants';
import { AuthenticationPanelLogic } from './authentication_panel_logic';
import { AUTHENTICATION_LABELS } from './constants';
export const AuthenticationPanelEditContent: React.FC = () => {
const { selectAuthOption, setHeaderContent, setPassword, setUsername } =
useActions(AuthenticationPanelLogic);
const { headerContent, username, password, selectedAuthOption } =
useValues(AuthenticationPanelLogic);
return (
<EuiFormFieldset>
<EuiFlexGroup direction="row">
<EuiFlexItem>
<EuiCheckableCard
id="basicAuthenticationCheckableCard"
className="authenticationCheckable"
label={
<EuiTitle size="xxs">
<h5>{AUTHENTICATION_LABELS.basic}</h5>
</EuiTitle>
}
value="basic"
checked={selectedAuthOption === 'basic'}
onChange={() => selectAuthOption('basic')}
>
<EuiForm>
<EuiFormRow label={USERNAME_LABEL}>
<EuiFieldText
value={username}
onChange={(event) => setUsername(event.target.value)}
disabled={selectedAuthOption !== 'basic'}
/>
</EuiFormRow>
<EuiFormRow label={PASSWORD_LABEL}>
<EuiFieldPassword
type="dual"
value={password}
onChange={(event) => setPassword(event.target.value)}
disabled={selectedAuthOption !== 'basic'}
/>
</EuiFormRow>
</EuiForm>
</EuiCheckableCard>
</EuiFlexItem>
<EuiFlexItem>
<EuiCheckableCard
id="authenticationHeaderCheckableCard"
className="authenticationCheckable"
label={
<EuiTitle size="xxs">
<h5>{AUTHENTICATION_LABELS.raw}</h5>
</EuiTitle>
}
value="raw"
checked={selectedAuthOption === 'raw'}
onChange={() => selectAuthOption('raw')}
>
<EuiForm>
<EuiFormRow label={TOKEN_LABEL}>
<EuiFieldPassword
type="dual"
value={headerContent}
onChange={(event) => setHeaderContent(event.target.value)}
disabled={selectedAuthOption !== 'raw'}
/>
</EuiFormRow>
</EuiForm>
</EuiCheckableCard>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormFieldset>
);
};

View file

@ -0,0 +1,134 @@
/*
* 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 { CrawlerAuth } from '../../../api/crawler/types';
import { isRawCrawlerAuth, isBasicCrawlerAuth } from '../../../api/crawler/utils';
import {
CrawlerDomainDetailActions,
CrawlerDomainDetailLogic,
} from '../crawler_domain_detail_logic';
interface AuthenticationPanelValues {
headerContent: string;
isEditing: boolean;
isModalVisible: boolean;
password: string;
selectedAuthOption: string | null;
username: string;
}
type AuthenticationPanelActions = {
deleteCredentials(): void;
disableEditing(): void;
enableEditing(currentCrawlerAuth?: CrawlerAuth): { currentCrawlerAuth: CrawlerAuth | undefined };
saveCredentials(): void;
selectAuthOption(authType: string): { authType: string };
setHeaderContent(headerContent: string): { headerContent: string };
setIsModalVisible(isModalVisible: boolean): { isModalVisible: boolean };
setPassword(password: string): { password: string };
setUsername(username: string): { username: string };
} & Pick<CrawlerDomainDetailActions, 'submitAuthUpdate' | 'receiveDomainData'>;
export const AuthenticationPanelLogic = kea<
MakeLogicType<AuthenticationPanelValues, AuthenticationPanelActions>
>({
path: ['enterprise_search', 'app_search', 'crawler', 'authentication_panel'],
connect: {
actions: [CrawlerDomainDetailLogic, ['submitAuthUpdate', 'receiveDomainData']],
},
actions: () => ({
deleteCredentials: true,
disableEditing: true,
enableEditing: (currentCrawlerAuth) => ({ currentCrawlerAuth }),
saveCredentials: true,
selectAuthOption: (authType) => ({ authType }),
setHeaderContent: (headerContent) => ({ headerContent }),
setIsModalVisible: (isModalVisible) => ({ isModalVisible }),
setPassword: (password) => ({ password }),
setUsername: (username) => ({ username }),
}),
reducers: () => ({
headerContent: [
'',
{
enableEditing: (_, { currentCrawlerAuth }) =>
currentCrawlerAuth !== undefined && isRawCrawlerAuth(currentCrawlerAuth)
? currentCrawlerAuth.header
: '',
receiveDomainData: () => '',
setHeaderContent: (_, { headerContent }) => headerContent,
},
],
isEditing: [
false,
{
disableEditing: () => false,
enableEditing: () => true,
receiveDomainData: () => false,
},
],
isModalVisible: [
false,
{
receiveDomainData: () => false,
setIsModalVisible: (_, { isModalVisible }) => isModalVisible,
},
],
password: [
'',
{
enableEditing: (_, { currentCrawlerAuth }) =>
currentCrawlerAuth !== undefined && isBasicCrawlerAuth(currentCrawlerAuth)
? currentCrawlerAuth.password
: '',
receiveDomainData: () => '',
setPassword: (_, { password }) => password,
},
],
selectedAuthOption: [
null,
{
enableEditing: (_, { currentCrawlerAuth }) => currentCrawlerAuth?.type ?? 'basic',
receiveDomainData: () => null,
selectAuthOption: (_, { authType }) => authType,
},
],
username: [
'',
{
enableEditing: (_, { currentCrawlerAuth }) =>
currentCrawlerAuth !== undefined && isBasicCrawlerAuth(currentCrawlerAuth)
? currentCrawlerAuth.username
: '',
receiveDomainData: () => '',
setUsername: (_, { username }) => username,
},
],
}),
listeners: ({ values }) => ({
saveCredentials: () => {
const { headerContent, password, selectedAuthOption, username } = values;
if (selectedAuthOption === 'basic') {
CrawlerDomainDetailLogic.actions.submitAuthUpdate({
password,
type: 'basic',
username,
});
} else if (selectedAuthOption === 'raw') {
CrawlerDomainDetailLogic.actions.submitAuthUpdate({
header: headerContent,
type: 'raw',
});
}
},
deleteCredentials: () => {
CrawlerDomainDetailLogic.actions.submitAuthUpdate(null);
},
}),
});

View file

@ -0,0 +1,76 @@
/*
* 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 { EuiEmptyPrompt, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { CrawlerDomainDetailLogic } from '../crawler_domain_detail_logic';
import './authentication_panel.scss';
export const AuthenticationPanelViewContent: React.FC = () => {
const { domain } = useValues(CrawlerDomainDetailLogic);
return domain?.auth ? (
<EuiPanel color="subdued" borderRadius="none" hasShadow={false}>
<EuiTitle size="xs">
<h3>
{i18n.translate(
'xpack.enterpriseSearch.crawler.authenticationPanel.configurationSavePanel.title',
{
defaultMessage: 'Configuration settings saved',
}
)}
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText color="subdued" size="s">
{i18n.translate(
'xpack.enterpriseSearch.crawler.authenticationPanel.configurationSavePanel.description',
{
defaultMessage:
'Authentication settings for crawling protected content have been saved. To update an authentication mechanism, delete settings and restart.',
}
)}
</EuiText>
</EuiPanel>
) : (
<EuiEmptyPrompt
title={
<h4>
{i18n.translate('xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.title', {
defaultMessage: 'No authentication configured',
})}
</h4>
}
body={
<FormattedMessage
id="xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.description"
defaultMessage="Click {addAuthenticationButtonLabel} to provide the credentials needed to crawl protected content"
values={{
addAuthenticationButtonLabel: (
<strong>
{i18n.translate(
'xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.addAuthenticationButtonLabel',
{
defaultMessage: 'Add authentication',
}
)}
</strong>
),
}}
/>
}
titleSize="s"
/>
);
};

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
export const AUTHENTICATION_LABELS = {
basic: i18n.translate(
'xpack.enterpriseSearch.crawler.authenticationPanel.basicAuthenticationLabel',
{
defaultMessage: 'Basic authentication',
}
),
raw: i18n.translate('xpack.enterpriseSearch.crawler.authenticationPanel.rawAuthenticationLabel', {
defaultMessage: 'Bearer authentication',
}),
};

View file

@ -28,6 +28,7 @@ import { SearchIndexTabId } from '../search_index/search_index';
import { baseBreadcrumbs } from '../search_indices';
import { CrawlerStatusIndicator } from '../shared/crawler_status_indicator/crawler_status_indicator';
import { AuthenticationPanel } from './authentication_panel/authentication_panel';
import { CrawlRulesTable } from './crawl_rules_table';
import { CrawlerDomainDetailLogic } from './crawler_domain_detail_logic';
import { DeduplicationPanel } from './deduplication_panel/deduplication_panel';
@ -96,6 +97,8 @@ export const CrawlerDomainDetail: React.FC = () => {
<EntryPointsTable domain={domain} indexName={indexName} items={domain.entryPoints} />
</EuiPanel>
<EuiSpacer />
<AuthenticationPanel />
<EuiSpacer />
<EuiPanel paddingSize="l" hasBorder>
<SitemapsTable domain={domain} indexName={indexName} items={domain.sitemaps} />
</EuiPanel>

View file

@ -21,6 +21,7 @@ import {
DeleteCrawlerDomainResponse,
} from '../../api/crawler/delete_crawler_domain_api_logic';
import {
CrawlerAuth,
CrawlerDomain,
CrawlerDomainFromServer,
CrawlRule,
@ -44,13 +45,14 @@ export interface CrawlerDomainDetailValues {
getLoading: boolean;
}
interface CrawlerDomainDetailActions {
export interface CrawlerDomainDetailActions {
deleteApiError(error: HttpError): HttpError;
deleteApiSuccess(response: DeleteCrawlerDomainResponse): DeleteCrawlerDomainResponse;
deleteDomain(): void;
deleteMakeRequest(args: DeleteCrawlerDomainArgs): DeleteCrawlerDomainArgs;
fetchDomainData(domainId: string): { domainId: string };
receiveDomainData(domain: CrawlerDomain): { domain: CrawlerDomain };
submitAuthUpdate(auth: CrawlerAuth): { auth: CrawlerAuth };
submitDeduplicationUpdate(payload: { enabled?: boolean; fields?: string[] }): {
enabled: boolean;
fields: string[];
@ -80,6 +82,7 @@ export const CrawlerDomainDetailLogic = kea<
deleteDomainComplete: () => true,
fetchDomainData: (domainId) => ({ domainId }),
receiveDomainData: (domain) => ({ domain }),
submitAuthUpdate: (auth) => ({ auth }),
submitDeduplicationUpdate: ({ fields, enabled }) => ({ enabled, fields }),
updateCrawlRules: (crawlRules) => ({ crawlRules }),
updateEntryPoints: (entryPoints) => ({ entryPoints }),
@ -135,7 +138,6 @@ export const CrawlerDomainDetailLogic = kea<
deleteApiError: (error) => {
flashAPIErrors(error);
},
fetchDomainData: async ({ domainId }) => {
const { http } = HttpLogic.values;
const { indexName } = IndexNameLogic.values;
@ -152,6 +154,30 @@ export const CrawlerDomainDetailLogic = kea<
flashAPIErrors(e);
}
},
submitAuthUpdate: async ({ auth }) => {
const { http } = HttpLogic.values;
const { indexName } = IndexNameLogic.values;
const { domainId } = values;
const payload = {
auth,
};
try {
const response = await http.put<CrawlerDomainFromServer>(
`/internal/enterprise_search/indices/${indexName}/crawler/domains/${domainId}`,
{
body: JSON.stringify(payload),
}
);
const domainData = crawlerDomainServerToClient(response);
actions.receiveDomainData(domainData);
} catch (e) {
flashAPIErrors(e);
}
},
submitDeduplicationUpdate: async ({ fields, enabled }) => {
const { http } = HttpLogic.values;
const { indexName } = IndexNameLogic.values;

View file

@ -25,6 +25,7 @@ describe('EntryPointsTable', () => {
{ id: '2', value: '/foo' },
];
const domain: CrawlerDomain = {
auth: null,
availableDeduplicationFields: ['title', 'description'],
crawlRules: [],
createdOn: '2018-01-01T00:00:00.000Z',

View file

@ -16,6 +16,8 @@ import { mountWithIntl } from '@kbn/test-jest-helpers';
import { GenericEndpointInlineEditableTable } from '../../../shared/tables/generic_endpoint_inline_editable_table';
import { CrawlerDomain } from '../../api/crawler/types';
import { SitemapsTable } from './sitemaps_table';
describe('SitemapsTable', () => {
@ -25,7 +27,8 @@ describe('SitemapsTable', () => {
{ id: '1', url: 'http://www.example.com/sitemap.xml' },
{ id: '2', url: 'http://www.example.com/whatever/sitemaps.xml' },
];
const domain = {
const domain: CrawlerDomain = {
auth: null,
createdOn: '2018-01-01T00:00:00.000Z',
documentCount: 10,
id: '6113e1407a2f2e6f42489794',

View file

@ -38,6 +38,7 @@ const domains: CrawlerDomain[] = [
deduplicationEnabled: false,
deduplicationFields: ['title'],
availableDeduplicationFields: ['title', 'description'],
auth: null,
},
{
id: '4567',
@ -50,6 +51,7 @@ const domains: CrawlerDomain[] = [
deduplicationEnabled: false,
deduplicationFields: ['title'],
availableDeduplicationFields: ['title', 'description'],
auth: null,
},
];

View file

@ -10,6 +10,19 @@ import { i18n } from '@kbn/i18n';
export const USERNAME_LABEL = i18n.translate('xpack.enterpriseSearch.usernameLabel', {
defaultMessage: 'Username',
});
export const PASSWORD_LABEL = i18n.translate('xpack.enterpriseSearch.passwordLabel', {
defaultMessage: 'Password',
});
export const TOKEN_LABEL = i18n.translate('xpack.enterpriseSearch.tokenLabel', {
defaultMessage: 'Token',
});
export const TYPE_LABEL = i18n.translate('xpack.enterpriseSearch.typeLabel', {
defaultMessage: 'Type',
});
export const EMAIL_LABEL = i18n.translate('xpack.enterpriseSearch.emailLabel', {
defaultMessage: 'Email',
});

View file

@ -9,7 +9,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import { EuiIcon, EuiButton, EuiTitle, EuiFlexGroup, EuiSpacer } from '@elastic/eui';
import { EuiIcon, EuiButton, EuiTitle, EuiSpacer } from '@elastic/eui';
import { LoadingOverlay } from '../loading';
@ -94,16 +94,6 @@ describe('DataPanel', () => {
expect(wrapper.find(EuiTitle).prop('size')).toEqual('s');
});
it('passes responsive to the header flex group', () => {
const wrapper = shallow(<DataPanel title={<h1>Test</h1>} />);
expect(wrapper.find(EuiFlexGroup).first().prop('responsive')).toEqual(false);
wrapper.setProps({ responsive: true });
expect(wrapper.find(EuiFlexGroup).first().prop('responsive')).toEqual(true);
});
it('renders panel color based on filled flag', () => {
const wrapper = shallow(<DataPanel title={<h1>Test</h1>} />);

View file

@ -32,7 +32,6 @@ type Props = Omit<_EuiPanelDivlike, 'title'> & {
iconType?: EuiIconProps['type'];
action?: React.ReactNode;
footerDocLink?: React.ReactNode;
responsive?: boolean;
filled?: boolean;
isLoading?: boolean;
className?: string;
@ -44,7 +43,6 @@ export const DataPanel: React.FC<Props> = ({
subtitle,
iconType,
action,
responsive = false,
filled,
isLoading,
footerDocLink,
@ -66,35 +64,29 @@ export const DataPanel: React.FC<Props> = ({
{...props}
>
<EuiSplitPanel.Inner>
<EuiFlexGroup direction="column" gutterSize="s" responsive={responsive}>
<EuiFlexItem>
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
<EuiFlexItem grow>
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
<EuiFlexItem grow>
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
{iconType && (
<EuiFlexItem grow={false}>
<EuiIcon type={iconType} />
</EuiFlexItem>
)}
<EuiFlexItem>
<EuiTitle size={titleSize}>{title}</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
{iconType && (
<EuiFlexItem grow={false}>
<EuiIcon type={iconType} />
</EuiFlexItem>
)}
<EuiFlexItem>
<EuiTitle size={titleSize}>{title}</EuiTitle>
</EuiFlexItem>
{action && <EuiFlexItem grow={false}>{action}</EuiFlexItem>}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
{subtitle && (
<>
<EuiSpacer size="s" />
<EuiText size="s" color="subdued">
<p>{subtitle}</p>
</EuiText>
</>
)}
</EuiFlexItem>
{action && <EuiFlexItem grow={false}>{action}</EuiFlexItem>}
</EuiFlexGroup>
{subtitle && (
<>
<EuiSpacer size="xs" />
<EuiText size="s" color="subdued">
<p>{subtitle}</p>
</EuiText>
</>
)}
{children && (
<>
<EuiSpacer size={filled || subtitle ? 'l' : 's'} />

View file

@ -260,6 +260,16 @@ export function registerCrawlerRoutes(routeDependencies: RouteDependencies) {
path: '/internal/enterprise_search/indices/{indexName}/crawler/domains/{domainId}',
validate: {
body: schema.object({
auth: schema.maybe(
schema.nullable(
schema.object({
header: schema.maybe(schema.string()),
password: schema.maybe(schema.string()),
type: schema.string(),
username: schema.maybe(schema.string()),
})
)
),
crawl_rules: schema.maybe(
schema.arrayOf(
schema.object({