mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Remote clusters] Add new security model (#161836)
This commit is contained in:
parent
56be6c6fb6
commit
6e241a8b02
25 changed files with 785 additions and 220 deletions
|
@ -391,6 +391,9 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => {
|
|||
remoteClusters: `${ELASTICSEARCH_DOCS}remote-clusters.html`,
|
||||
remoteClustersProxy: `${ELASTICSEARCH_DOCS}remote-clusters.html#proxy-mode`,
|
||||
remoteClusersProxySettings: `${ELASTICSEARCH_DOCS}remote-clusters-settings.html#remote-cluster-proxy-settings`,
|
||||
remoteClustersOnPremSetupTrustWithCert: `${ELASTICSEARCH_DOCS}remote-clusters-cert.html`,
|
||||
remoteClustersOnPremSetupTrustWithApiKey: `${ELASTICSEARCH_DOCS}remote-clusters-api-key.html`,
|
||||
remoteClustersCloudSetupTrust: `${ELASTIC_WEBSITE_URL}guide/en/cloud/current/ec-enable-ccs.html`,
|
||||
rrf: `${ELASTICSEARCH_DOCS}rrf.html`,
|
||||
scriptParameters: `${ELASTICSEARCH_DOCS}modules-scripting-using.html#prefer-params`,
|
||||
secureCluster: `${ELASTICSEARCH_DOCS}secure-cluster.html`,
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
# Remote Clusters
|
||||
|
||||
## Setting up a remote cluster
|
||||
|
||||
* Run `yarn es snapshot --license=trial` and log into kibana
|
||||
* Create a local copy of the ES snapshot with `cp -R .es/8.10.0 .es/8.10.0-2`
|
||||
* Start your local copy of the ES snapshot .es/8.10.0-2/bin/elasticsearch -E cluster.name=europe -E transport.port=9400
|
||||
* Create a remote cluster using `127.0.0.1:9400` as seed node and proceed with the wizard
|
||||
* Verify that your newly created remote cluster is shown as connected in the remote clusters list
|
||||
|
||||
## About
|
||||
|
||||
This plugin helps users manage their [remote clusters](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-remote-clusters.html), which enable cross-cluster search and cross-cluster replication.
|
|
@ -179,6 +179,63 @@ describe('Create Remote cluster', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Setup Trust', () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
({ actions, component } = await setup(httpSetup, {
|
||||
canUseAPIKeyTrustModel: true,
|
||||
}));
|
||||
});
|
||||
|
||||
component.update();
|
||||
|
||||
actions.nameInput.setValue('remote_cluster_test');
|
||||
actions.seedsInput.setValue('192.168.1.1:3000');
|
||||
|
||||
await actions.saveButton.click();
|
||||
});
|
||||
|
||||
test('should contain two cards for setting up trust', () => {
|
||||
// Cards exist
|
||||
expect(actions.setupTrust.apiCardExist()).toBe(true);
|
||||
expect(actions.setupTrust.certCardExist()).toBe(true);
|
||||
// Each card has its doc link
|
||||
expect(actions.setupTrust.apiCardDocsExist()).toBe(true);
|
||||
expect(actions.setupTrust.certCardDocsExist()).toBe(true);
|
||||
});
|
||||
|
||||
test('on submit should open confirm modal', async () => {
|
||||
await actions.setupTrust.setupTrustConfirmClick();
|
||||
|
||||
expect(actions.setupTrust.isSubmitInConfirmDisabled()).toBe(true);
|
||||
await actions.setupTrust.toggleConfirmSwitch();
|
||||
expect(actions.setupTrust.isSubmitInConfirmDisabled()).toBe(false);
|
||||
});
|
||||
|
||||
test('back button goes to first step', async () => {
|
||||
await actions.setupTrust.backToFirstStepClick();
|
||||
expect(actions.isOnFirstStep()).toBe(true);
|
||||
});
|
||||
|
||||
test('shows only cert based config if API key trust model is not available', async () => {
|
||||
await act(async () => {
|
||||
({ actions, component } = await setup(httpSetup, {
|
||||
canUseAPIKeyTrustModel: false,
|
||||
}));
|
||||
});
|
||||
|
||||
component.update();
|
||||
|
||||
actions.nameInput.setValue('remote_cluster_test');
|
||||
actions.seedsInput.setValue('192.168.1.1:3000');
|
||||
|
||||
await actions.saveButton.click();
|
||||
|
||||
expect(actions.setupTrust.apiCardExist()).toBe(false);
|
||||
expect(actions.setupTrust.certCardExist()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on prem', () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
|
|
|
@ -49,10 +49,21 @@ export interface RemoteClustersActions {
|
|||
getLabel: () => string;
|
||||
exists: () => boolean;
|
||||
};
|
||||
isOnFirstStep: () => boolean;
|
||||
saveButton: {
|
||||
click: () => void;
|
||||
isDisabled: () => boolean;
|
||||
};
|
||||
setupTrust: {
|
||||
isSubmitInConfirmDisabled: () => boolean;
|
||||
toggleConfirmSwitch: () => void;
|
||||
setupTrustConfirmClick: () => void;
|
||||
backToFirstStepClick: () => void;
|
||||
apiCardExist: () => boolean;
|
||||
certCardExist: () => boolean;
|
||||
apiCardDocsExist: () => boolean;
|
||||
certCardDocsExist: () => boolean;
|
||||
};
|
||||
getErrorMessages: () => string[];
|
||||
globalErrorExists: () => boolean;
|
||||
}
|
||||
|
@ -148,7 +159,7 @@ export const createRemoteClustersActions = (testBed: TestBed): RemoteClustersAct
|
|||
};
|
||||
};
|
||||
|
||||
const createSaveButtonActions = () => {
|
||||
const createSetupTrustActions = () => {
|
||||
const click = () => {
|
||||
act(() => {
|
||||
find('remoteClusterFormSaveButton').simulate('click');
|
||||
|
@ -157,7 +168,56 @@ export const createRemoteClustersActions = (testBed: TestBed): RemoteClustersAct
|
|||
component.update();
|
||||
};
|
||||
const isDisabled = () => find('remoteClusterFormSaveButton').props().disabled;
|
||||
return { saveButton: { click, isDisabled } };
|
||||
|
||||
const setupTrustConfirmClick = () => {
|
||||
act(() => {
|
||||
find('setupTrustDoneButton').simulate('click');
|
||||
});
|
||||
|
||||
component.update();
|
||||
};
|
||||
|
||||
const backToFirstStepClick = () => {
|
||||
act(() => {
|
||||
find('setupTrustBackButton').simulate('click');
|
||||
});
|
||||
|
||||
component.update();
|
||||
};
|
||||
|
||||
const isOnFirstStep = () => exists('remoteClusterFormNameInput');
|
||||
|
||||
const toggleConfirmSwitch = () => {
|
||||
act(() => {
|
||||
const $checkbox = find('remoteClusterTrustCheckbox');
|
||||
const isChecked = $checkbox.props().checked;
|
||||
$checkbox.simulate('change', { target: { checked: !isChecked } });
|
||||
});
|
||||
|
||||
component.update();
|
||||
};
|
||||
|
||||
const isSubmitInConfirmDisabled = () => find('remoteClusterTrustSubmitButton').props().disabled;
|
||||
|
||||
const apiCardExist = () => exists('setupTrustApiKeyCard');
|
||||
const certCardExist = () => exists('setupTrustCertCard');
|
||||
const apiCardDocsExist = () => exists('setupTrustApiKeyCardDocs');
|
||||
const certCardDocsExist = () => exists('setupTrustCertCardDocs');
|
||||
|
||||
return {
|
||||
isOnFirstStep,
|
||||
saveButton: { click, isDisabled },
|
||||
setupTrust: {
|
||||
setupTrustConfirmClick,
|
||||
isSubmitInConfirmDisabled,
|
||||
toggleConfirmSwitch,
|
||||
apiCardExist,
|
||||
certCardExist,
|
||||
apiCardDocsExist,
|
||||
certCardDocsExist,
|
||||
backToFirstStepClick,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createServerNameActions = () => {
|
||||
|
@ -192,7 +252,7 @@ export const createRemoteClustersActions = (testBed: TestBed): RemoteClustersAct
|
|||
...createCloudUrlInputActions(),
|
||||
...createProxyAddressActions(),
|
||||
...createServerNameActions(),
|
||||
...createSaveButtonActions(),
|
||||
...createSetupTrustActions(),
|
||||
getErrorMessages: form.getErrorsMessages,
|
||||
globalErrorExists,
|
||||
};
|
||||
|
|
|
@ -35,6 +35,7 @@ export const WithAppDependencies =
|
|||
isCloudEnabled: !!isCloudEnabled,
|
||||
cloudBaseUrl: 'test.com',
|
||||
executionContext: executionContextServiceMock.createStartContract(),
|
||||
canUseAPIKeyTrustModel: true,
|
||||
...overrides,
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -12,6 +12,7 @@ export interface Context {
|
|||
isCloudEnabled: boolean;
|
||||
cloudBaseUrl: string;
|
||||
executionContext: ExecutionContextStart;
|
||||
canUseAPIKeyTrustModel: boolean;
|
||||
}
|
||||
|
||||
export const AppContext = createContext<Context>({} as any);
|
||||
|
|
|
@ -16,6 +16,7 @@ export declare const renderApp: (
|
|||
isCloudEnabled: boolean;
|
||||
cloudBaseUrl: string;
|
||||
executionContext: ExecutionContextStart;
|
||||
canUseAPIKeyTrustModel: boolean;
|
||||
},
|
||||
history: ScopedHistory,
|
||||
theme$: Observable<CoreTheme>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { RemoteClusterSetupTrust } from './remote_cluster_setup_trust';
|
||||
export { RemoteClusterForm } from './remote_cluster_form';
|
||||
export { RemoteClusterPageTitle } from './remote_cluster_page_title';
|
||||
export { ConfiguredByNodeWarning } from './configured_by_node_warning';
|
||||
|
|
|
@ -21,11 +21,9 @@ import {
|
|||
EuiFormRow,
|
||||
EuiLink,
|
||||
EuiLoadingLogo,
|
||||
EuiLoadingSpinner,
|
||||
EuiOverlayMask,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiDelayRender,
|
||||
EuiScreenReaderOnly,
|
||||
|
@ -302,87 +300,67 @@ export class RemoteClusterForm extends Component<Props, State> {
|
|||
}
|
||||
|
||||
renderActions() {
|
||||
const { isSaving, cancel } = this.props;
|
||||
const { isSaving, cancel, cluster: isEditMode } = this.props;
|
||||
const { areErrorsVisible, isRequestVisible } = this.state;
|
||||
|
||||
if (isSaving) {
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="flexStart" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="l" />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.actions.savingText"
|
||||
defaultMessage="Saving"
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
let cancelButton;
|
||||
|
||||
if (cancel) {
|
||||
cancelButton = (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty color="primary" onClick={cancel}>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
|
||||
const isSaveDisabled = areErrorsVisible && this.hasErrors();
|
||||
const isSaveDisabled = (areErrorsVisible && this.hasErrors()) || isSaving;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
{cancel && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty color="primary" onClick={cancel}>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
onClick={this.toggleRequest}
|
||||
data-test-subj="remoteClustersRequestButton"
|
||||
>
|
||||
{isRequestVisible ? (
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.hideRequestButtonLabel"
|
||||
defaultMessage="Hide request"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.showRequestButtonLabel"
|
||||
defaultMessage="Show request"
|
||||
/>
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="remoteClusterFormSaveButton"
|
||||
color="success"
|
||||
iconType="check"
|
||||
color="primary"
|
||||
onClick={this.save}
|
||||
fill
|
||||
isDisabled={isSaveDisabled}
|
||||
isLoading={isSaving}
|
||||
aria-describedby={`${this.generateId(ERROR_TITLE_ID)} ${this.generateId(
|
||||
ERROR_LIST_ID
|
||||
)}`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.saveButtonLabel"
|
||||
defaultMessage="Save"
|
||||
id="xpack.remoteClusters.remoteClusterForm.nextButtonLabel"
|
||||
defaultMessage="{isEditMode, select, true{Save} other{Next}}"
|
||||
values={{
|
||||
isEditMode: Boolean(isEditMode),
|
||||
}}
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
|
||||
{cancelButton}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={this.toggleRequest} data-test-subj="remoteClustersRequestButton">
|
||||
{isRequestVisible ? (
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.hideRequestButtonLabel"
|
||||
defaultMessage="Hide request"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.showRequestButtonLabel"
|
||||
defaultMessage="Show request"
|
||||
/>
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
@ -523,20 +501,20 @@ export class RemoteClusterForm extends Component<Props, State> {
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" data-test-subj="remoteClusterFormGlobalError" />
|
||||
<EuiCallOut
|
||||
title={
|
||||
<h3 id={this.generateId(ERROR_TITLE_ID)}>
|
||||
<span id={this.generateId(ERROR_TITLE_ID)}>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.errorTitle"
|
||||
defaultMessage="Fix errors before continuing."
|
||||
defaultMessage="Some fields require your attention."
|
||||
/>
|
||||
</h3>
|
||||
</span>
|
||||
}
|
||||
color="danger"
|
||||
iconType="cross"
|
||||
iconType="error"
|
||||
/>
|
||||
<EuiDelayRender>{messagesToBeRendered}</EuiDelayRender>
|
||||
<EuiSpacer size="m" data-test-subj="remoteClusterFormGlobalError" />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
@ -549,6 +527,7 @@ export class RemoteClusterForm extends Component<Props, State> {
|
|||
return (
|
||||
<Fragment>
|
||||
{this.renderSaveErrorFeedback()}
|
||||
{this.renderErrors()}
|
||||
|
||||
<EuiForm data-test-subj="remoteClusterForm">
|
||||
<EuiDescribedFormGroup
|
||||
|
@ -609,8 +588,6 @@ export class RemoteClusterForm extends Component<Props, State> {
|
|||
{this.renderSkipUnavailable()}
|
||||
</EuiForm>
|
||||
|
||||
{this.renderErrors()}
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
{this.renderActions()}
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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, { useState, FormEvent } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiButtonEmpty,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiModal,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiCheckbox,
|
||||
useGeneratedHtmlId,
|
||||
} from '@elastic/eui';
|
||||
|
||||
interface ModalProps {
|
||||
closeModal: () => void;
|
||||
onSubmit: () => void;
|
||||
}
|
||||
|
||||
export const ConfirmTrustSetupModal = ({ closeModal, onSubmit }: ModalProps) => {
|
||||
const [hasSetupTrust, setHasSetupTrust] = useState<boolean>(false);
|
||||
const modalFormId = useGeneratedHtmlId({ prefix: 'modalForm' });
|
||||
const checkBoxId = useGeneratedHtmlId({ prefix: 'checkBoxId' });
|
||||
|
||||
const onFormSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
closeModal();
|
||||
onSubmit();
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiModal onClose={closeModal}>
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.clusterWizard.trustStep.modal.title"
|
||||
defaultMessage="Confirm your configuration"
|
||||
/>
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
|
||||
<EuiModalBody>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.clusterWizard.trustStep.body"
|
||||
defaultMessage="Have you set up trust to connect to your remote cluster?"
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiForm id={modalFormId} component="form" onSubmit={onFormSubmit}>
|
||||
<EuiFormRow>
|
||||
<EuiCheckbox
|
||||
id={checkBoxId}
|
||||
label={i18n.translate('xpack.remoteClusters.clusterWizard.trustStep.modal.checkbox', {
|
||||
defaultMessage: 'Yes, I have setup trust',
|
||||
})}
|
||||
checked={hasSetupTrust}
|
||||
onChange={() => setHasSetupTrust(!hasSetupTrust)}
|
||||
data-test-subj="remoteClusterTrustCheckbox"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
</EuiModalBody>
|
||||
|
||||
<EuiModalFooter>
|
||||
<EuiButtonEmpty onClick={closeModal}>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.clusterWizard.trustStep.modal.cancelButton"
|
||||
defaultMessage="No, go back"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
||||
<EuiButton
|
||||
fill
|
||||
type="submit"
|
||||
form={modalFormId}
|
||||
disabled={!hasSetupTrust}
|
||||
data-test-subj="remoteClusterTrustSubmitButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.clusterWizard.trustStep.modal.createCluster"
|
||||
defaultMessage="Add remote cluster"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { RemoteClusterSetupTrust } from './remote_cluster_setup_trust';
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* 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, { useState, useContext } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiSpacer,
|
||||
EuiCard,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import * as docs from '../../../services/documentation';
|
||||
import { AppContext } from '../../../app_context';
|
||||
import { ConfirmTrustSetupModal } from './confirm_modal';
|
||||
|
||||
const MIN_ALLOWED_VERSION_API_KEYS_METHOD = '8.10';
|
||||
const CARD_MAX_WIDTH = 400;
|
||||
const i18nTexts = {
|
||||
apiKeyTitle: i18n.translate(
|
||||
'xpack.remoteClusters.clusterWizard.trustStep.setupWithApiKeys.title',
|
||||
{ defaultMessage: 'API keys' }
|
||||
),
|
||||
apiKeyBadge: i18n.translate(
|
||||
'xpack.remoteClusters.clusterWizard.trustStep.setupWithApiKeys.badge',
|
||||
{ defaultMessage: 'BETA' }
|
||||
),
|
||||
apiKeyDescription: i18n.translate(
|
||||
'xpack.remoteClusters.clusterWizard.trustStep.setupWithApiKeys.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Fine-grained access to remote indices. You need an API key provided by the remote cluster administrator.',
|
||||
}
|
||||
),
|
||||
certTitle: i18n.translate('xpack.remoteClusters.clusterWizard.trustStep.setupWithCert.title', {
|
||||
defaultMessage: 'Certificates',
|
||||
}),
|
||||
certDescription: i18n.translate(
|
||||
'xpack.remoteClusters.clusterWizard.trustStep.setupWithCert.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Full access to the remote cluster. You need TLS certificates from the remote cluster.',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
const docLinks = {
|
||||
cert: docs.onPremSetupTrustWithCertUrl,
|
||||
apiKey: docs.onPremSetupTrustWithApiKeyUrl,
|
||||
cloud: docs.cloudSetupTrustUrl,
|
||||
};
|
||||
|
||||
interface Props {
|
||||
onBack: () => void;
|
||||
onSubmit: () => void;
|
||||
isSaving: boolean;
|
||||
}
|
||||
|
||||
export const RemoteClusterSetupTrust = ({ onBack, onSubmit, isSaving }: Props) => {
|
||||
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
|
||||
const { canUseAPIKeyTrustModel, isCloudEnabled } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<EuiText size="m" textAlign="center">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.clusterWizard.trustStep.title"
|
||||
defaultMessage="Set up an authentication mechanism to connect to the remote cluster. Complete{br} this step using the instructions in our docs before continuing."
|
||||
values={{
|
||||
br: <br />,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="xxl" />
|
||||
|
||||
<EuiFlexGroup wrap justifyContent="center">
|
||||
{canUseAPIKeyTrustModel && (
|
||||
<EuiFlexItem style={{ maxWidth: CARD_MAX_WIDTH }}>
|
||||
<EuiCard
|
||||
title={i18nTexts.apiKeyTitle}
|
||||
paddingSize="l"
|
||||
betaBadgeProps={{ label: i18nTexts.apiKeyBadge, color: 'accent' }}
|
||||
data-test-subj="setupTrustApiKeyCard"
|
||||
>
|
||||
<EuiText size="s">
|
||||
<p>{i18nTexts.apiKeyDescription}</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="xl" />
|
||||
<EuiButton
|
||||
href={isCloudEnabled ? docLinks.cloud : docLinks.apiKey}
|
||||
target="_blank"
|
||||
data-test-subj="setupTrustApiKeyCardDocs"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.clusterWizard.trustStep.docs"
|
||||
defaultMessage="View instructions"
|
||||
/>
|
||||
</EuiButton>
|
||||
<EuiSpacer size="xl" />
|
||||
<EuiText size="xs" color="subdued">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.clusterWizard.trustStep.apiKeyNote"
|
||||
defaultMessage="Both clusters must be on version {minAllowedVersion} or above."
|
||||
values={{ minAllowedVersion: MIN_ALLOWED_VERSION_API_KEYS_METHOD }}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiCard>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiFlexItem style={{ maxWidth: CARD_MAX_WIDTH }}>
|
||||
<EuiCard
|
||||
title={
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
{i18nTexts.certTitle}
|
||||
</>
|
||||
}
|
||||
paddingSize="l"
|
||||
data-test-subj="setupTrustCertCard"
|
||||
>
|
||||
<EuiText size="s">
|
||||
<p>{i18nTexts.certDescription}</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="xl" />
|
||||
<EuiButton
|
||||
href={isCloudEnabled ? docLinks.cloud : docLinks.cert}
|
||||
target="_blank"
|
||||
data-test-subj="setupTrustCertCardDocs"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.clusterWizard.trustStep.docs"
|
||||
defaultMessage="View instructions"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiCard>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="xxl" />
|
||||
|
||||
<EuiFlexGroup wrap justifyContent="center">
|
||||
<EuiFlexItem style={{ maxWidth: CARD_MAX_WIDTH }}>
|
||||
<EuiFlexGroup justifyContent="flexStart">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="setupTrustBackButton"
|
||||
iconType="arrowLeft"
|
||||
onClick={onBack}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.clusterWizard.trustStep.backButtonLabel"
|
||||
defaultMessage="Back"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ maxWidth: CARD_MAX_WIDTH }}>
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="setupTrustDoneButton"
|
||||
color="primary"
|
||||
fill
|
||||
isLoading={isSaving}
|
||||
onClick={() => setIsModalVisible(true)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.clusterWizard.trustStep.doneButtonLabel"
|
||||
defaultMessage="Add remote cluster"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
{isModalVisible && (
|
||||
<ConfirmTrustSetupModal closeModal={() => setIsModalVisible(false)} onSubmit={onSubmit} />
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -8,11 +8,13 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiPageSection, EuiPageBody } from '@elastic/eui';
|
||||
|
||||
import { extractQueryParams } from '../../../shared_imports';
|
||||
import { getRouter, redirect } from '../../services';
|
||||
import { setBreadcrumbs } from '../../services/breadcrumb';
|
||||
import { RemoteClusterPageTitle, RemoteClusterForm } from '../components';
|
||||
import { RemoteClusterPageTitle } from '../components';
|
||||
import { RemoteClusterWizard } from './wizard_form';
|
||||
|
||||
export class RemoteClusterAdd extends PureComponent {
|
||||
static propTypes = {
|
||||
|
@ -35,7 +37,7 @@ export class RemoteClusterAdd extends PureComponent {
|
|||
this.props.addCluster(clusterConfig);
|
||||
};
|
||||
|
||||
cancel = () => {
|
||||
redirectToList = () => {
|
||||
const {
|
||||
history,
|
||||
route: {
|
||||
|
@ -56,29 +58,31 @@ export class RemoteClusterAdd extends PureComponent {
|
|||
const { isAddingCluster, addClusterError } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<RemoteClusterPageTitle
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.addTitle"
|
||||
defaultMessage="Add remote cluster"
|
||||
/>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClustersDescription"
|
||||
defaultMessage="Add a remote cluster that connects to seed nodes or to a single proxy address."
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<EuiPageBody data-test-subj="remote-clusters-add">
|
||||
<EuiPageSection paddingSize="none">
|
||||
<RemoteClusterPageTitle
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.addTitle"
|
||||
defaultMessage="Add remote cluster"
|
||||
/>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClustersDescription"
|
||||
defaultMessage="Create a connection from this cluster to other Elasticsearch clusters."
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<RemoteClusterForm
|
||||
isSaving={isAddingCluster}
|
||||
saveError={addClusterError}
|
||||
save={this.save}
|
||||
cancel={this.cancel}
|
||||
/>
|
||||
</>
|
||||
<RemoteClusterWizard
|
||||
saveRemoteClusterConfig={this.save}
|
||||
onCancel={this.redirectToList}
|
||||
isSaving={isAddingCluster}
|
||||
addClusterError={addClusterError}
|
||||
/>
|
||||
</EuiPageSection>
|
||||
</EuiPageBody>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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, { useState, useMemo, useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiStepsHorizontal, EuiStepStatus, EuiSpacer, EuiPageSection } from '@elastic/eui';
|
||||
|
||||
import { RemoteClusterSetupTrust, RemoteClusterForm } from '../components';
|
||||
import { Cluster } from '../../../../common/lib/cluster_serialization';
|
||||
|
||||
const CONFIGURE_CONNECTION = 1;
|
||||
const SETUP_TRUST = 2;
|
||||
|
||||
interface Props {
|
||||
saveRemoteClusterConfig: (config: Cluster) => void;
|
||||
onCancel: () => void;
|
||||
addClusterError: { message: string } | undefined;
|
||||
isSaving: boolean;
|
||||
}
|
||||
|
||||
export const RemoteClusterWizard = ({
|
||||
saveRemoteClusterConfig,
|
||||
onCancel,
|
||||
isSaving,
|
||||
addClusterError,
|
||||
}: Props) => {
|
||||
const [formState, setFormState] = useState<Cluster>();
|
||||
const [currentStep, setCurrentStep] = useState(CONFIGURE_CONNECTION);
|
||||
|
||||
// If there was an error saving the cluster, we need
|
||||
// to send the user back to the first step.
|
||||
useEffect(() => {
|
||||
if (addClusterError) {
|
||||
setCurrentStep(CONFIGURE_CONNECTION);
|
||||
}
|
||||
}, [addClusterError, setCurrentStep]);
|
||||
|
||||
const stepDefinitions = useMemo(
|
||||
() => [
|
||||
{
|
||||
step: CONFIGURE_CONNECTION,
|
||||
title: i18n.translate('xpack.remoteClusters.clusterWizard.addConnectionInfoLabel', {
|
||||
defaultMessage: 'Add connection information',
|
||||
}),
|
||||
status: (currentStep === CONFIGURE_CONNECTION ? 'current' : 'complete') as EuiStepStatus,
|
||||
onClick: () => setCurrentStep(CONFIGURE_CONNECTION),
|
||||
},
|
||||
{
|
||||
step: SETUP_TRUST,
|
||||
title: i18n.translate('xpack.remoteClusters.clusterWizard.setupTrustLabel', {
|
||||
defaultMessage: 'Establish trust',
|
||||
}),
|
||||
status: (currentStep === SETUP_TRUST ? 'current' : 'incomplete') as EuiStepStatus,
|
||||
disabled: !formState,
|
||||
onClick: () => setCurrentStep(SETUP_TRUST),
|
||||
},
|
||||
],
|
||||
[currentStep, formState, setCurrentStep]
|
||||
);
|
||||
|
||||
// Upon finalizing configuring the connection, we need to temporarily store the
|
||||
// cluster configuration so that we can persist it when the user completes the
|
||||
// trust step.
|
||||
const completeConfigStep = (clusterConfig: Cluster) => {
|
||||
setFormState(clusterConfig);
|
||||
setCurrentStep(SETUP_TRUST);
|
||||
};
|
||||
|
||||
const completeTrustStep = () => {
|
||||
saveRemoteClusterConfig(formState as Cluster);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPageSection restrictWidth>
|
||||
<EuiStepsHorizontal steps={stepDefinitions} />
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
{/*
|
||||
Instead of unmounting the Form, we toggle its visibility not to lose the form
|
||||
state when moving to the next step.
|
||||
*/}
|
||||
<div style={{ display: currentStep === CONFIGURE_CONNECTION ? 'block' : 'none' }}>
|
||||
<RemoteClusterForm
|
||||
save={completeConfigStep}
|
||||
cancel={onCancel}
|
||||
saveError={addClusterError}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{currentStep === SETUP_TRUST && (
|
||||
<RemoteClusterSetupTrust
|
||||
onBack={() => setCurrentStep(CONFIGURE_CONNECTION)}
|
||||
onSubmit={completeTrustStep}
|
||||
isSaving={isSaving}
|
||||
/>
|
||||
)}
|
||||
</EuiPageSection>
|
||||
);
|
||||
};
|
|
@ -12,8 +12,9 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import {
|
||||
EuiButton,
|
||||
EuiCallOut,
|
||||
EuiEmptyPrompt,
|
||||
EuiPageContent_Deprecated as EuiPageContent,
|
||||
EuiPageTemplate,
|
||||
EuiPageSection,
|
||||
EuiPageBody,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
|
@ -95,22 +96,23 @@ export class RemoteClusterEdit extends Component {
|
|||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center" color="subdued">
|
||||
<EuiPageTemplate minHeight={0} panelled paddingSize="none" offset={0}>
|
||||
<SectionLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.loadingLabel"
|
||||
defaultMessage="Loading remote cluster…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
</EuiPageContent>
|
||||
</EuiPageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
if (!cluster) {
|
||||
return (
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center" color="danger">
|
||||
<EuiEmptyPrompt
|
||||
<EuiPageTemplate minHeight={0} panelled paddingSize="none" offset={0}>
|
||||
<EuiPageTemplate.EmptyPrompt
|
||||
iconType="warning"
|
||||
color="danger"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
|
@ -142,7 +144,7 @@ export class RemoteClusterEdit extends Component {
|
|||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
</EuiPageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -150,8 +152,8 @@ export class RemoteClusterEdit extends Component {
|
|||
|
||||
if (isConfiguredByNode) {
|
||||
return (
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center" color="primary">
|
||||
<EuiEmptyPrompt
|
||||
<EuiPageTemplate minHeight={0} panelled paddingSize="none" offset={0}>
|
||||
<EuiPageTemplate.EmptyPrompt
|
||||
iconType="iInCircle"
|
||||
title={
|
||||
<h2>
|
||||
|
@ -179,50 +181,52 @@ export class RemoteClusterEdit extends Component {
|
|||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
</EuiPageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<RemoteClusterPageTitle
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.editTitle"
|
||||
defaultMessage="Edit remote cluster"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{hasDeprecatedProxySetting ? (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.deprecatedSettingsTitle"
|
||||
defaultMessage="Proceed with caution"
|
||||
/>
|
||||
}
|
||||
color="warning"
|
||||
iconType="help"
|
||||
>
|
||||
<EuiPageBody restrictWidth={true} data-test-subj="remote-clusters-edit">
|
||||
<EuiPageSection paddingSize="none">
|
||||
<RemoteClusterPageTitle
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.deprecatedSettingsMessage"
|
||||
defaultMessage="This remote cluster has deprecated settings that we tried to resolve. Verify all changes before saving."
|
||||
id="xpack.remoteClusters.editTitle"
|
||||
defaultMessage="Edit remote cluster"
|
||||
/>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
) : null}
|
||||
}
|
||||
/>
|
||||
|
||||
<RemoteClusterForm
|
||||
cluster={cluster}
|
||||
isSaving={isEditingCluster}
|
||||
saveError={getEditClusterError}
|
||||
save={this.save}
|
||||
cancel={this.cancel}
|
||||
/>
|
||||
</>
|
||||
{hasDeprecatedProxySetting ? (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.deprecatedSettingsTitle"
|
||||
defaultMessage="Proceed with caution"
|
||||
/>
|
||||
}
|
||||
color="warning"
|
||||
iconType="help"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.deprecatedSettingsMessage"
|
||||
defaultMessage="This remote cluster has deprecated settings that we tried to resolve. Verify all changes before saving."
|
||||
/>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<RemoteClusterForm
|
||||
cluster={cluster}
|
||||
isSaving={isEditingCluster}
|
||||
saveError={getEditClusterError}
|
||||
save={this.save}
|
||||
cancel={this.cancel}
|
||||
/>
|
||||
</EuiPageSection>
|
||||
</EuiPageBody>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,28 +9,11 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiIconTip, EuiText } from '@elastic/eui';
|
||||
import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
|
||||
|
||||
import { SNIFF_MODE, PROXY_MODE } from '../../../../../../common/constants';
|
||||
|
||||
export function ConnectionStatus({ isConnected, mode }) {
|
||||
let icon;
|
||||
let message;
|
||||
|
||||
if (isConnected) {
|
||||
icon = <EuiIcon type="check" color="success" />;
|
||||
|
||||
message = i18n.translate('xpack.remoteClusters.connectedStatus.connectedAriaLabel', {
|
||||
defaultMessage: 'Connected',
|
||||
});
|
||||
} else {
|
||||
icon = <EuiIcon type="cross" color="danger" />;
|
||||
|
||||
message = i18n.translate('xpack.remoteClusters.connectedStatus.notConnectedAriaLabel', {
|
||||
defaultMessage: 'Not connected',
|
||||
});
|
||||
}
|
||||
|
||||
const seedNodeTooltip = i18n.translate(
|
||||
'xpack.remoteClusters.connectedStatus.notConnectedToolTip',
|
||||
{
|
||||
|
@ -41,15 +24,18 @@ export function ConnectionStatus({ isConnected, mode }) {
|
|||
return (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<span data-test-subj="remoteClusterConnectionStatusIcon" className="eui-displayBlock">
|
||||
{icon}
|
||||
</span>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false} className="remoteClustersConnectionStatus__message">
|
||||
<EuiText data-test-subj="remoteClusterConnectionStatusMessage" size="s">
|
||||
{message}
|
||||
</EuiText>
|
||||
<EuiBadge
|
||||
color={isConnected ? 'success' : 'danger'}
|
||||
data-test-subj="remoteClusterConnectionStatusMessage"
|
||||
>
|
||||
{isConnected
|
||||
? i18n.translate('xpack.remoteClusters.connectedStatus.connectedAriaLabel', {
|
||||
defaultMessage: 'Connected',
|
||||
})
|
||||
: i18n.translate('xpack.remoteClusters.connectedStatus.notConnectedAriaLabel', {
|
||||
defaultMessage: 'Not connected',
|
||||
})}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
|
||||
{!isConnected && mode === SNIFF_MODE && (
|
||||
|
|
|
@ -12,12 +12,15 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiEmptyPrompt,
|
||||
EuiLoadingLogo,
|
||||
EuiOverlayMask,
|
||||
EuiPageContent_Deprecated as EuiPageContent,
|
||||
EuiSpacer,
|
||||
EuiPageHeader,
|
||||
EuiPageSection,
|
||||
EuiPageBody,
|
||||
EuiPageTemplate,
|
||||
EuiTitle,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { remoteClustersUrl } from '../../services/documentation';
|
||||
|
@ -90,9 +93,10 @@ export class RemoteClusterList extends Component {
|
|||
|
||||
renderNoPermission() {
|
||||
return (
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center" color="danger">
|
||||
<EuiEmptyPrompt
|
||||
<EuiPageTemplate minHeight={0} panelled paddingSize="none" offset={0}>
|
||||
<EuiPageTemplate.EmptyPrompt
|
||||
iconType="warning"
|
||||
color="danger"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
|
@ -110,7 +114,7 @@ export class RemoteClusterList extends Component {
|
|||
</p>
|
||||
}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
</EuiPageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -120,9 +124,10 @@ export class RemoteClusterList extends Component {
|
|||
const { statusCode, error: errorString } = error.body;
|
||||
|
||||
return (
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center" color="danger">
|
||||
<EuiEmptyPrompt
|
||||
<EuiPageTemplate minHeight={0} panelled paddingSize="none" offset={0}>
|
||||
<EuiPageTemplate.EmptyPrompt
|
||||
iconType="warning"
|
||||
color="danger"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
|
@ -137,14 +142,14 @@ export class RemoteClusterList extends Component {
|
|||
</p>
|
||||
}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
</EuiPageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
renderEmpty() {
|
||||
return (
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center" color="subdued">
|
||||
<EuiEmptyPrompt
|
||||
<EuiPageTemplate minHeight={0} panelled paddingSize="none" offset={0}>
|
||||
<EuiPageTemplate.EmptyPrompt
|
||||
data-test-subj="remoteClusterListEmptyPrompt"
|
||||
iconType="managementApp"
|
||||
title={
|
||||
|
@ -177,17 +182,36 @@ export class RemoteClusterList extends Component {
|
|||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
footer={
|
||||
<>
|
||||
<EuiTitle size="xxs">
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusters.emptyState.docsDescription"
|
||||
defaultMessage="Want to learn more?"
|
||||
/>
|
||||
</span>
|
||||
</EuiTitle>{' '}
|
||||
<EuiLink href={remoteClustersUrl} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusters.emptyState.docsLink"
|
||||
defaultMessage="Read documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
</EuiPageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
renderLoading() {
|
||||
return (
|
||||
<EuiPageContent
|
||||
verticalPosition="center"
|
||||
horizontalPosition="center"
|
||||
color="subdued"
|
||||
<EuiPageTemplate
|
||||
minHeight={0}
|
||||
panelled
|
||||
paddingSize="none"
|
||||
offset={0}
|
||||
data-test-subj="remoteClustersTableLoading"
|
||||
>
|
||||
<SectionLoading>
|
||||
|
@ -196,7 +220,7 @@ export class RemoteClusterList extends Component {
|
|||
defaultMessage="Loading remote clusters…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
</EuiPageContent>
|
||||
</EuiPageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -204,35 +228,37 @@ export class RemoteClusterList extends Component {
|
|||
const { clusters } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPageHeader
|
||||
bottomBorder
|
||||
pageTitle={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterListTitle"
|
||||
defaultMessage="Remote Clusters"
|
||||
/>
|
||||
}
|
||||
rightSideItems={[
|
||||
<EuiButtonEmpty
|
||||
href={remoteClustersUrl}
|
||||
target="_blank"
|
||||
iconType="help"
|
||||
data-test-subj="documentationLink"
|
||||
>
|
||||
<EuiPageBody data-test-subj="remote-clusters-list">
|
||||
<EuiPageSection paddingSize="none">
|
||||
<EuiPageHeader
|
||||
bottomBorder
|
||||
pageTitle={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClustersDocsLinkText"
|
||||
defaultMessage="Remote Clusters docs"
|
||||
id="xpack.remoteClusters.remoteClusterListTitle"
|
||||
defaultMessage="Remote Clusters"
|
||||
/>
|
||||
</EuiButtonEmpty>,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
rightSideItems={[
|
||||
<EuiButtonEmpty
|
||||
href={remoteClustersUrl}
|
||||
target="_blank"
|
||||
iconType="help"
|
||||
data-test-subj="documentationLink"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClustersDocsLinkText"
|
||||
defaultMessage="Remote Clusters docs"
|
||||
/>
|
||||
</EuiButtonEmpty>,
|
||||
]}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<RemoteClusterTable clusters={clusters} />
|
||||
<DetailPanel />
|
||||
</>
|
||||
<RemoteClusterTable clusters={clusters} />
|
||||
<DetailPanel />
|
||||
</EuiPageSection>
|
||||
</EuiPageBody>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -256,7 +282,6 @@ export class RemoteClusterList extends Component {
|
|||
} else {
|
||||
content = this.renderList();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{content}
|
||||
|
|
|
@ -235,7 +235,7 @@ export class RemoteClusterTable extends Component {
|
|||
name: i18n.translate('xpack.remoteClusters.remoteClusterList.table.addressesColumnTitle', {
|
||||
defaultMessage: 'Addresses',
|
||||
}),
|
||||
dataTestSubj: 'remoteClustersAddress',
|
||||
'data-test-subj': 'remoteClustersAddress',
|
||||
truncateText: true,
|
||||
render: (mode, { seeds, proxyAddress }) => {
|
||||
const clusterAddressString = mode === PROXY_MODE ? proxyAddress : seeds.join(', ');
|
||||
|
@ -259,6 +259,7 @@ export class RemoteClusterTable extends Component {
|
|||
),
|
||||
sortable: true,
|
||||
width: '160px',
|
||||
align: 'right',
|
||||
render: (mode, { connectedNodesCount, connectedSocketsCount }) => {
|
||||
const remoteNodesCount =
|
||||
mode === PROXY_MODE ? connectedSocketsCount : connectedNodesCount;
|
||||
|
|
|
@ -12,6 +12,9 @@ export let remoteClustersUrl: string;
|
|||
export let transportPortUrl: string;
|
||||
export let proxyModeUrl: string;
|
||||
export let proxySettingsUrl: string;
|
||||
export let onPremSetupTrustWithCertUrl: string;
|
||||
export let onPremSetupTrustWithApiKeyUrl: string;
|
||||
export let cloudSetupTrustUrl: string;
|
||||
|
||||
export function init({ links }: DocLinksStart): void {
|
||||
skippingDisconnectedClustersUrl = links.ccs.skippingDisconnectedClusters;
|
||||
|
@ -19,4 +22,7 @@ export function init({ links }: DocLinksStart): void {
|
|||
transportPortUrl = links.elasticsearch.transportSettings;
|
||||
proxyModeUrl = links.elasticsearch.remoteClustersProxy;
|
||||
proxySettingsUrl = links.elasticsearch.remoteClusersProxySettings;
|
||||
onPremSetupTrustWithCertUrl = links.elasticsearch.remoteClustersOnPremSetupTrustWithCert;
|
||||
onPremSetupTrustWithApiKeyUrl = links.elasticsearch.remoteClustersOnPremSetupTrustWithApiKey;
|
||||
cloudSetupTrustUrl = links.elasticsearch.remoteClustersCloudSetupTrust;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CoreSetup, Plugin, CoreStart, PluginInitializerContext } from '@kbn/core/public';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { PLUGIN } from '../common/constants';
|
||||
import { init as initBreadcrumbs } from './application/services/breadcrumb';
|
||||
|
@ -27,6 +28,9 @@ export class RemoteClustersUIPlugin
|
|||
{
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {}
|
||||
|
||||
private canUseApiKeyTrustModel: boolean = false;
|
||||
private licensingSubscription?: Subscription;
|
||||
|
||||
setup(
|
||||
{ notifications: { toasts }, http, getStartServices }: CoreSetup,
|
||||
{ management, usageCollection, cloud, share }: Dependencies
|
||||
|
@ -70,7 +74,12 @@ export class RemoteClustersUIPlugin
|
|||
const unmountAppCallback = await renderApp(
|
||||
element,
|
||||
i18nContext,
|
||||
{ isCloudEnabled, cloudBaseUrl, executionContext },
|
||||
{
|
||||
isCloudEnabled,
|
||||
cloudBaseUrl,
|
||||
executionContext,
|
||||
canUseAPIKeyTrustModel: this.canUseApiKeyTrustModel,
|
||||
},
|
||||
history,
|
||||
theme$
|
||||
);
|
||||
|
@ -94,7 +103,7 @@ export class RemoteClustersUIPlugin
|
|||
};
|
||||
}
|
||||
|
||||
start({ application }: CoreStart) {
|
||||
start({ application }: CoreStart, { licensing }: Dependencies) {
|
||||
const {
|
||||
ui: { enabled: isRemoteClustersUiEnabled },
|
||||
} = this.initializerContext.config.get<ClientConfigType>();
|
||||
|
@ -102,7 +111,13 @@ export class RemoteClustersUIPlugin
|
|||
if (isRemoteClustersUiEnabled) {
|
||||
initRedirect(application.navigateToApp);
|
||||
}
|
||||
|
||||
this.licensingSubscription = licensing.license$.subscribe((next) => {
|
||||
this.canUseApiKeyTrustModel = next.hasAtLeast('enterprise');
|
||||
});
|
||||
}
|
||||
|
||||
stop() {}
|
||||
stop() {
|
||||
this.licensingSubscription?.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,14 @@ import { RegisterManagementAppArgs } from '@kbn/management-plugin/public';
|
|||
import { SharePluginSetup } from '@kbn/share-plugin/public';
|
||||
import { I18nStart } from '@kbn/core/public';
|
||||
import { CloudSetup } from '@kbn/cloud-plugin/public';
|
||||
import { LicensingPluginStart } from '@kbn/licensing-plugin/public';
|
||||
|
||||
export interface Dependencies {
|
||||
management: ManagementSetup;
|
||||
usageCollection: UsageCollectionSetup;
|
||||
cloud: CloudSetup;
|
||||
share: SharePluginSetup;
|
||||
licensing: LicensingPluginStart;
|
||||
}
|
||||
|
||||
export interface ClientConfigType {
|
||||
|
|
|
@ -28422,7 +28422,6 @@
|
|||
"xpack.remoteClusters.listBreadcrumbTitle": "Clusters distants",
|
||||
"xpack.remoteClusters.readDocsButtonLabel": "Documents du cluster distant",
|
||||
"xpack.remoteClusters.refreshAction.errorTitle": "Erreur lors de l'actualisation des clusters distants",
|
||||
"xpack.remoteClusters.remoteClusterForm.actions.savingText": "Enregistrement",
|
||||
"xpack.remoteClusters.remoteClusterForm.addressError.invalidPortMessage": "Un port est requis.",
|
||||
"xpack.remoteClusters.remoteClusterForm.cancelButtonLabel": "Annuler",
|
||||
"xpack.remoteClusters.remoteClusterForm.cloudUrlHelp.buttonLabel": "Besoin d'aide ?",
|
||||
|
@ -28458,7 +28457,6 @@
|
|||
"xpack.remoteClusters.remoteClusterForm.manualModeFieldLabel": "Entrer manuellement l'adresse proxy et le nom du serveur",
|
||||
"xpack.remoteClusters.remoteClusterForm.proxyError.invalidCharactersMessage": "L'adresse doit utiliser le format host:port. Exemple : 127.0.0.1:9400, localhost:9400. Les hôtes ne peuvent comprendre que des lettres, des chiffres et des tirets.",
|
||||
"xpack.remoteClusters.remoteClusterForm.proxyError.missingProxyMessage": "Une adresse proxy est requise.",
|
||||
"xpack.remoteClusters.remoteClusterForm.saveButtonLabel": "Enregistrer",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeCloudDescription": "Configurez automatiquement le cluster distant à l'aide de l'URL de point de terminaison Elasticsearch du déploiement distant ou entrez l'adresse proxy et le nom du serveur manuellement.",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeDescription": "Utilisez les nœuds initiaux par défaut, ou passez au mode proxy.",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeTitle": "Mode de connexion",
|
||||
|
|
|
@ -28422,7 +28422,6 @@
|
|||
"xpack.remoteClusters.listBreadcrumbTitle": "リモートクラスター",
|
||||
"xpack.remoteClusters.readDocsButtonLabel": "リモートクラスタードキュメント",
|
||||
"xpack.remoteClusters.refreshAction.errorTitle": "リモートクラスターの更新中にエラーが発生",
|
||||
"xpack.remoteClusters.remoteClusterForm.actions.savingText": "保存中",
|
||||
"xpack.remoteClusters.remoteClusterForm.addressError.invalidPortMessage": "ポートが必要です。",
|
||||
"xpack.remoteClusters.remoteClusterForm.cancelButtonLabel": "キャンセル",
|
||||
"xpack.remoteClusters.remoteClusterForm.cloudUrlHelp.buttonLabel": "ヘルプが必要な場合",
|
||||
|
@ -28458,7 +28457,6 @@
|
|||
"xpack.remoteClusters.remoteClusterForm.manualModeFieldLabel": "手動でプロキシアドレスとサーバー名を入力",
|
||||
"xpack.remoteClusters.remoteClusterForm.proxyError.invalidCharactersMessage": "アドレスはホスト:ポートの形式にする必要があります。例:127.0.0.1:9400、localhost:9400ホストには文字、数字、ハイフンのみが使用できます。",
|
||||
"xpack.remoteClusters.remoteClusterForm.proxyError.missingProxyMessage": "プロキシアドレスが必要です。",
|
||||
"xpack.remoteClusters.remoteClusterForm.saveButtonLabel": "保存",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeCloudDescription": "リモートデプロイのElasticsearchエンドポイントURLを使用して、リモートクラスターを自動的に構成するか、プロキシアドレスとサーバー名を手動で入力します。",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeDescription": "既定でシードノードを使用するか、プロキシモードに切り替えます。",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeTitle": "接続モード",
|
||||
|
|
|
@ -28420,7 +28420,6 @@
|
|||
"xpack.remoteClusters.listBreadcrumbTitle": "远程集群",
|
||||
"xpack.remoteClusters.readDocsButtonLabel": "远程集群文档",
|
||||
"xpack.remoteClusters.refreshAction.errorTitle": "刷新远程集群时出错",
|
||||
"xpack.remoteClusters.remoteClusterForm.actions.savingText": "正在保存",
|
||||
"xpack.remoteClusters.remoteClusterForm.addressError.invalidPortMessage": "端口必填。",
|
||||
"xpack.remoteClusters.remoteClusterForm.cancelButtonLabel": "取消",
|
||||
"xpack.remoteClusters.remoteClusterForm.cloudUrlHelp.buttonLabel": "需要帮助?",
|
||||
|
@ -28456,7 +28455,6 @@
|
|||
"xpack.remoteClusters.remoteClusterForm.manualModeFieldLabel": "手动输入代理地址和服务器名称",
|
||||
"xpack.remoteClusters.remoteClusterForm.proxyError.invalidCharactersMessage": "地址必须使用 host:port 格式。例如:127.0.0.1:9400、localhost:9400。主机只能由字母、数字和短划线构成。",
|
||||
"xpack.remoteClusters.remoteClusterForm.proxyError.missingProxyMessage": "必须指定代理地址。",
|
||||
"xpack.remoteClusters.remoteClusterForm.saveButtonLabel": "保存",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeCloudDescription": "通过使用远程部署的 Elasticsearch 终端 URL 自动配置运程集群或手动输入代理地址和服务器名称。",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeDescription": "默认使用种子节点或切换到代理模式。",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeTitle": "连接模式",
|
||||
|
|
|
@ -29,7 +29,14 @@ export function RemoteClustersPageProvider({ getService }: FtrProviderContext) {
|
|||
});
|
||||
await testSubjects.setValue('remoteClusterFormNameInput', name);
|
||||
await comboBox.setCustom('comboBoxInput', seedNode);
|
||||
|
||||
// Submit config form
|
||||
await testSubjects.click('remoteClusterFormSaveButton');
|
||||
|
||||
// Complete trust setup
|
||||
await testSubjects.click('setupTrustDoneButton');
|
||||
await testSubjects.setCheckbox('remoteClusterTrustCheckbox', 'check');
|
||||
await testSubjects.click('remoteClusterTrustSubmitButton');
|
||||
},
|
||||
async getRemoteClustersList() {
|
||||
const table = await testSubjects.find('remoteClusterListTable');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue