mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Enterprise Search] Allow user to set authentication for Elastic Web Crawler domains (#140432)
This commit is contained in:
parent
b4d09651a7
commit
73ba80778e
20 changed files with 655 additions and 49 deletions
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
.authenticationPanel {
|
||||
.authenticationCheckable {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
|
@ -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 />}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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);
|
||||
},
|
||||
}),
|
||||
});
|
|
@ -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"
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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',
|
||||
}),
|
||||
};
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
|
|
@ -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>} />);
|
||||
|
||||
|
|
|
@ -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'} />
|
||||
|
|
|
@ -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({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue