mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[Remote Cluster] Improve UX (#206958)](https://github.com/elastic/kibana/pull/206958) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Sonia Sanz Vivas","email":"sonia.sanzvivas@elastic.co"},"sourceCommit":{"committedDate":"2025-01-28T16:55:32Z","message":"[Remote Cluster] Improve UX (#206958)\n\nCloses https://github.com/elastic/kibana/issues/199664\nPart of https://github.com/elastic/kibana/issues/205027 (removes .scss\nfile)\n## Summary\n\n<details>\n <summary>Screenshots</summary>\n\n **Add remote cluster in Cloud**\n\n<img width=\"1512\" alt=\"Screenshot 2025-01-28 at 10 34 23\"\nsrc=\"https://github.com/user-attachments/assets/b8cf24be-8e04-4629-939c-c8d781a727e4\"\n/>\n\n<img width=\"1500\" alt=\"Screenshot 2025-01-28 at 10 36 03\"\nsrc=\"https://github.com/user-attachments/assets/612dbc29-f5cb-4809-a4e7-95a99cd2643a\"\n/>\n\n\n**Cloud/Certificate**\n\n<img width=\"1502\" alt=\"Screenshot 2025-01-28 at 10 36 39\"\nsrc=\"https://github.com/user-attachments/assets/b174735f-41ce-4fdb-872e-5db26e3400e0\"\n/>\n\n**Cloud/API**\n\n<img width=\"1505\" alt=\"Screenshot 2025-01-28 at 10 36 28\"\nsrc=\"https://github.com/user-attachments/assets/c6c03589-1bf8-4e47-8c8d-2ab8da897c4e\"\n/>\n\n**Edit remote cluster in Cloud**\n\n<img width=\"1506\" alt=\"Screenshot 2025-01-28 at 10 37 47\"\nsrc=\"https://github.com/user-attachments/assets/bc74e3fb-28e8-41ca-90d9-c3b09c9b8532\"\n/>\n\n**Add remote cluster in Stateful/API Key and Cert**\n\n<img width=\"1501\" alt=\"Screenshot 2025-01-28 at 10 31 36\"\nsrc=\"https://github.com/user-attachments/assets/2ed78cc3-2d5a-4090-9b3d-fd6c78b73c00\"\n/>\n<img width=\"1503\" alt=\"Screenshot 2025-01-28 at 10 32 02\"\nsrc=\"https://github.com/user-attachments/assets/33487816-613e-4e8b-8ff4-dfb73ff4c67c\"\n/>\n<img width=\"756\" alt=\"Screenshot 2025-01-28 at 10 32 13\"\nsrc=\"https://github.com/user-attachments/assets/e387f6e8-51f0-4b8e-859d-88f5112897f9\"\n/>\n\n**Edit remote cluster in Stateful**\n\n<img width=\"1505\" alt=\"Screenshot 2025-01-28 at 10 31 08\"\nsrc=\"https://github.com/user-attachments/assets/e176dd9e-0336-412e-a231-a07f92a6ae0f\"\n/>\n</details>\n\n### About this PR\n\n#### Typescript\nAll files in this PR have been created in typescript or moved to\ntypescript. It has been intended that everything follows strict typing\nbut in two cases: `RemoteClusterAdd.container` and\n`RemoteClusterEdit.container`. In these cases it has been chosen to add\ntype `any()`, which is basically the same as it already existed when\nthey were `.js` files. The reason for not adding strict typing to these\nfiles is that it would add more noise to this PR and is something that\ncan be done as a follow-up.\n\nNo files were migrated to typescript that did not directly affect the\nchanges within the scope of this PR.\n\n#### Styles\nI removed `_hacks.scss`. The only class that was in use was\n`remoteClusterSkipIfUnavailableSwitch`, and only the `padding-top`\napplied. I moved this padding to `emotion` in the `remote_cluster_form`\nfile, using `euiTheme`.\n\nI realized that the plugin has some `classNames` in other files that\ndoesn't exist in any style file. I'm pretty sure it's dead code and can\nbe removed in a follow-up.\n\n#### Possible follow-ups\n* Remove dead css classes\n* Add types to `RemoteClusterAdd.container` and\n`RemoteClusterEdit.container`\n* Migrate the rest of the files in the plugin to typescript\n* Move RequestFlyout to\n[ViewApiRequestFlyout](cfb1997d7b/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout.tsx (L55)
)\n* Add Functional test that actually connects the clusters\n\n### How to test\n\n### Checklist\n\n- [x] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [x] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\nhttps://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7776","sha":"7b5d6031023d30ddf9e2080222036c1048c73366","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:CCR and Remote Clusters","Team:Kibana Management","release_note:skip","v9.0.0","backport:prev-minor","ci:cloud-deploy"],"title":"[Remote Cluster] Improve UX","number":206958,"url":"https://github.com/elastic/kibana/pull/206958","mergeCommit":{"message":"[Remote Cluster] Improve UX (#206958)\n\nCloses https://github.com/elastic/kibana/issues/199664\nPart of https://github.com/elastic/kibana/issues/205027 (removes .scss\nfile)\n## Summary\n\n<details>\n <summary>Screenshots</summary>\n\n **Add remote cluster in Cloud**\n\n<img width=\"1512\" alt=\"Screenshot 2025-01-28 at 10 34 23\"\nsrc=\"https://github.com/user-attachments/assets/b8cf24be-8e04-4629-939c-c8d781a727e4\"\n/>\n\n<img width=\"1500\" alt=\"Screenshot 2025-01-28 at 10 36 03\"\nsrc=\"https://github.com/user-attachments/assets/612dbc29-f5cb-4809-a4e7-95a99cd2643a\"\n/>\n\n\n**Cloud/Certificate**\n\n<img width=\"1502\" alt=\"Screenshot 2025-01-28 at 10 36 39\"\nsrc=\"https://github.com/user-attachments/assets/b174735f-41ce-4fdb-872e-5db26e3400e0\"\n/>\n\n**Cloud/API**\n\n<img width=\"1505\" alt=\"Screenshot 2025-01-28 at 10 36 28\"\nsrc=\"https://github.com/user-attachments/assets/c6c03589-1bf8-4e47-8c8d-2ab8da897c4e\"\n/>\n\n**Edit remote cluster in Cloud**\n\n<img width=\"1506\" alt=\"Screenshot 2025-01-28 at 10 37 47\"\nsrc=\"https://github.com/user-attachments/assets/bc74e3fb-28e8-41ca-90d9-c3b09c9b8532\"\n/>\n\n**Add remote cluster in Stateful/API Key and Cert**\n\n<img width=\"1501\" alt=\"Screenshot 2025-01-28 at 10 31 36\"\nsrc=\"https://github.com/user-attachments/assets/2ed78cc3-2d5a-4090-9b3d-fd6c78b73c00\"\n/>\n<img width=\"1503\" alt=\"Screenshot 2025-01-28 at 10 32 02\"\nsrc=\"https://github.com/user-attachments/assets/33487816-613e-4e8b-8ff4-dfb73ff4c67c\"\n/>\n<img width=\"756\" alt=\"Screenshot 2025-01-28 at 10 32 13\"\nsrc=\"https://github.com/user-attachments/assets/e387f6e8-51f0-4b8e-859d-88f5112897f9\"\n/>\n\n**Edit remote cluster in Stateful**\n\n<img width=\"1505\" alt=\"Screenshot 2025-01-28 at 10 31 08\"\nsrc=\"https://github.com/user-attachments/assets/e176dd9e-0336-412e-a231-a07f92a6ae0f\"\n/>\n</details>\n\n### About this PR\n\n#### Typescript\nAll files in this PR have been created in typescript or moved to\ntypescript. It has been intended that everything follows strict typing\nbut in two cases: `RemoteClusterAdd.container` and\n`RemoteClusterEdit.container`. In these cases it has been chosen to add\ntype `any()`, which is basically the same as it already existed when\nthey were `.js` files. The reason for not adding strict typing to these\nfiles is that it would add more noise to this PR and is something that\ncan be done as a follow-up.\n\nNo files were migrated to typescript that did not directly affect the\nchanges within the scope of this PR.\n\n#### Styles\nI removed `_hacks.scss`. The only class that was in use was\n`remoteClusterSkipIfUnavailableSwitch`, and only the `padding-top`\napplied. I moved this padding to `emotion` in the `remote_cluster_form`\nfile, using `euiTheme`.\n\nI realized that the plugin has some `classNames` in other files that\ndoesn't exist in any style file. I'm pretty sure it's dead code and can\nbe removed in a follow-up.\n\n#### Possible follow-ups\n* Remove dead css classes\n* Add types to `RemoteClusterAdd.container` and\n`RemoteClusterEdit.container`\n* Migrate the rest of the files in the plugin to typescript\n* Move RequestFlyout to\n[ViewApiRequestFlyout](cfb1997d7b/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout.tsx (L55)
)\n* Add Functional test that actually connects the clusters\n\n### How to test\n\n### Checklist\n\n- [x] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [x] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\nhttps://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7776","sha":"7b5d6031023d30ddf9e2080222036c1048c73366"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/206958","number":206958,"mergeCommit":{"message":"[Remote Cluster] Improve UX (#206958)\n\nCloses https://github.com/elastic/kibana/issues/199664\nPart of https://github.com/elastic/kibana/issues/205027 (removes .scss\nfile)\n## Summary\n\n<details>\n <summary>Screenshots</summary>\n\n **Add remote cluster in Cloud**\n\n<img width=\"1512\" alt=\"Screenshot 2025-01-28 at 10 34 23\"\nsrc=\"https://github.com/user-attachments/assets/b8cf24be-8e04-4629-939c-c8d781a727e4\"\n/>\n\n<img width=\"1500\" alt=\"Screenshot 2025-01-28 at 10 36 03\"\nsrc=\"https://github.com/user-attachments/assets/612dbc29-f5cb-4809-a4e7-95a99cd2643a\"\n/>\n\n\n**Cloud/Certificate**\n\n<img width=\"1502\" alt=\"Screenshot 2025-01-28 at 10 36 39\"\nsrc=\"https://github.com/user-attachments/assets/b174735f-41ce-4fdb-872e-5db26e3400e0\"\n/>\n\n**Cloud/API**\n\n<img width=\"1505\" alt=\"Screenshot 2025-01-28 at 10 36 28\"\nsrc=\"https://github.com/user-attachments/assets/c6c03589-1bf8-4e47-8c8d-2ab8da897c4e\"\n/>\n\n**Edit remote cluster in Cloud**\n\n<img width=\"1506\" alt=\"Screenshot 2025-01-28 at 10 37 47\"\nsrc=\"https://github.com/user-attachments/assets/bc74e3fb-28e8-41ca-90d9-c3b09c9b8532\"\n/>\n\n**Add remote cluster in Stateful/API Key and Cert**\n\n<img width=\"1501\" alt=\"Screenshot 2025-01-28 at 10 31 36\"\nsrc=\"https://github.com/user-attachments/assets/2ed78cc3-2d5a-4090-9b3d-fd6c78b73c00\"\n/>\n<img width=\"1503\" alt=\"Screenshot 2025-01-28 at 10 32 02\"\nsrc=\"https://github.com/user-attachments/assets/33487816-613e-4e8b-8ff4-dfb73ff4c67c\"\n/>\n<img width=\"756\" alt=\"Screenshot 2025-01-28 at 10 32 13\"\nsrc=\"https://github.com/user-attachments/assets/e387f6e8-51f0-4b8e-859d-88f5112897f9\"\n/>\n\n**Edit remote cluster in Stateful**\n\n<img width=\"1505\" alt=\"Screenshot 2025-01-28 at 10 31 08\"\nsrc=\"https://github.com/user-attachments/assets/e176dd9e-0336-412e-a231-a07f92a6ae0f\"\n/>\n</details>\n\n### About this PR\n\n#### Typescript\nAll files in this PR have been created in typescript or moved to\ntypescript. It has been intended that everything follows strict typing\nbut in two cases: `RemoteClusterAdd.container` and\n`RemoteClusterEdit.container`. In these cases it has been chosen to add\ntype `any()`, which is basically the same as it already existed when\nthey were `.js` files. The reason for not adding strict typing to these\nfiles is that it would add more noise to this PR and is something that\ncan be done as a follow-up.\n\nNo files were migrated to typescript that did not directly affect the\nchanges within the scope of this PR.\n\n#### Styles\nI removed `_hacks.scss`. The only class that was in use was\n`remoteClusterSkipIfUnavailableSwitch`, and only the `padding-top`\napplied. I moved this padding to `emotion` in the `remote_cluster_form`\nfile, using `euiTheme`.\n\nI realized that the plugin has some `classNames` in other files that\ndoesn't exist in any style file. I'm pretty sure it's dead code and can\nbe removed in a follow-up.\n\n#### Possible follow-ups\n* Remove dead css classes\n* Add types to `RemoteClusterAdd.container` and\n`RemoteClusterEdit.container`\n* Migrate the rest of the files in the plugin to typescript\n* Move RequestFlyout to\n[ViewApiRequestFlyout](cfb1997d7b/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout.tsx (L55)
)\n* Add Functional test that actually connects the clusters\n\n### How to test\n\n### Checklist\n\n- [x] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [x] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\nhttps://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7776","sha":"7b5d6031023d30ddf9e2080222036c1048c73366"}}]}] BACKPORT--> Co-authored-by: Sonia Sanz Vivas <sonia.sanzvivas@elastic.co>
This commit is contained in:
parent
c1c77285a1
commit
5779a170d5
55 changed files with 2034 additions and 1625 deletions
|
@ -12,6 +12,12 @@ import { setupEnvironment, RemoteClustersActions } from '../helpers';
|
|||
import { setup } from './remote_clusters_add.helpers';
|
||||
import { NON_ALPHA_NUMERIC_CHARS, ACCENTED_CHARS } from './special_characters';
|
||||
import { MAX_NODE_CONNECTIONS } from '../../../common/constants';
|
||||
import {
|
||||
onPremPrerequisitesApiKey,
|
||||
onPremPrerequisitesCert,
|
||||
onPremSecurityApiKey,
|
||||
onPremSecurityCert,
|
||||
} from '../../../public/application/services/documentation';
|
||||
|
||||
const notInArray = (array: string[]) => (value: string) => array.indexOf(value) < 0;
|
||||
|
||||
|
@ -38,186 +44,20 @@ describe('Create Remote cluster', () => {
|
|||
expect(actions.docsButtonExists()).toBe(true);
|
||||
});
|
||||
|
||||
test('should have a toggle to Skip unavailable remote cluster', () => {
|
||||
expect(actions.skipUnavailableSwitch.exists()).toBe(true);
|
||||
|
||||
// By default it should be set to "true"
|
||||
expect(actions.skipUnavailableSwitch.isChecked()).toBe(true);
|
||||
|
||||
actions.skipUnavailableSwitch.toggle();
|
||||
|
||||
expect(actions.skipUnavailableSwitch.isChecked()).toBe(false);
|
||||
});
|
||||
|
||||
describe('on prem', () => {
|
||||
test('should have a toggle to enable "proxy" mode for a remote cluster', () => {
|
||||
expect(actions.connectionModeSwitch.exists()).toBe(true);
|
||||
|
||||
// By default it should be set to "false"
|
||||
expect(actions.connectionModeSwitch.isChecked()).toBe(false);
|
||||
|
||||
actions.connectionModeSwitch.toggle();
|
||||
|
||||
expect(actions.connectionModeSwitch.isChecked()).toBe(true);
|
||||
});
|
||||
|
||||
test('server name has optional label', () => {
|
||||
actions.connectionModeSwitch.toggle();
|
||||
expect(actions.serverNameInput.getLabel()).toBe('Server name (optional)');
|
||||
});
|
||||
|
||||
test('should display errors and disable the save button when clicking "save" without filling the form', async () => {
|
||||
expect(actions.globalErrorExists()).toBe(false);
|
||||
expect(actions.saveButton.isDisabled()).toBe(false);
|
||||
|
||||
await actions.saveButton.click();
|
||||
|
||||
expect(actions.globalErrorExists()).toBe(true);
|
||||
expect(actions.getErrorMessages()).toEqual([
|
||||
'Name is required.',
|
||||
// seeds input is switched on by default on prem and is required
|
||||
'At least one seed node is required.',
|
||||
]);
|
||||
expect(actions.saveButton.isDisabled()).toBe(true);
|
||||
});
|
||||
|
||||
test('renders no switch for cloud advanced options', () => {
|
||||
expect(actions.cloudAdvancedOptionsSwitch.exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
describe('on cloud', () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
({ actions, component } = await setup(httpSetup, { isCloudEnabled: true }));
|
||||
});
|
||||
|
||||
component.update();
|
||||
});
|
||||
|
||||
test('TLS server name has optional label', () => {
|
||||
actions.cloudAdvancedOptionsSwitch.toggle();
|
||||
expect(actions.tlsServerNameInput.getLabel()).toBe('TLS server name (optional)');
|
||||
});
|
||||
|
||||
test('renders a switch for advanced options', () => {
|
||||
expect(actions.cloudAdvancedOptionsSwitch.exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('renders no switch between sniff and proxy modes', () => {
|
||||
expect(actions.connectionModeSwitch.exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('advanced options are initially disabled', () => {
|
||||
expect(actions.cloudAdvancedOptionsSwitch.isChecked()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('form validation', () => {
|
||||
describe('remote cluster name', () => {
|
||||
test('should not allow spaces', async () => {
|
||||
actions.nameInput.setValue('with space');
|
||||
|
||||
await actions.saveButton.click();
|
||||
|
||||
expect(actions.getErrorMessages()).toContain('Spaces are not allowed in the name.');
|
||||
});
|
||||
|
||||
test('should only allow alpha-numeric characters, "-" (dash) and "_" (underscore)', async () => {
|
||||
const expectInvalidChar = (char: string) => {
|
||||
if (char === '-' || char === '_') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
actions.nameInput.setValue(`with${char}`);
|
||||
|
||||
expect(actions.getErrorMessages()).toContain(
|
||||
`Remove the character ${char} from the name.`
|
||||
);
|
||||
} catch {
|
||||
throw Error(`Char "${char}" expected invalid but was allowed`);
|
||||
}
|
||||
};
|
||||
|
||||
await actions.saveButton.click(); // display form errors
|
||||
|
||||
[...NON_ALPHA_NUMERIC_CHARS, ...ACCENTED_CHARS].forEach(expectInvalidChar);
|
||||
});
|
||||
});
|
||||
|
||||
describe('proxy address', () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
({ actions, component } = await setup(httpSetup));
|
||||
});
|
||||
|
||||
component.update();
|
||||
|
||||
actions.connectionModeSwitch.toggle();
|
||||
});
|
||||
|
||||
test('should only allow alpha-numeric characters and "-" (dash) in the proxy address "host" part', async () => {
|
||||
await actions.saveButton.click(); // display form errors
|
||||
|
||||
const expectInvalidChar = (char: string) => {
|
||||
actions.proxyAddressInput.setValue(`192.16${char}:3000`);
|
||||
expect(actions.getErrorMessages()).toContain(
|
||||
'Address must use host:port format. Example: 127.0.0.1:9400, localhost:9400. Hosts can only consist of letters, numbers, and dashes.'
|
||||
);
|
||||
};
|
||||
|
||||
[...NON_ALPHA_NUMERIC_CHARS, ...ACCENTED_CHARS]
|
||||
.filter(notInArray(['-', '_', ':']))
|
||||
.forEach(expectInvalidChar);
|
||||
});
|
||||
|
||||
test('should require a numeric "port" to be set', async () => {
|
||||
await actions.saveButton.click();
|
||||
|
||||
actions.proxyAddressInput.setValue('192.168.1.1');
|
||||
expect(actions.getErrorMessages()).toContain('A port is required.');
|
||||
|
||||
actions.proxyAddressInput.setValue('192.168.1.1:abc');
|
||||
expect(actions.getErrorMessages()).toContain('A port is required.');
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
expect(actions.setupTrustStep.apiCardExist()).toBe(true);
|
||||
expect(actions.setupTrustStep.certCardExist()).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('next button should be disabled if not trust mode selected', async () => {
|
||||
expect(actions.setupTrustStep.button.isDisabled()).toBe(true);
|
||||
});
|
||||
|
||||
test('back button goes to first step', async () => {
|
||||
await actions.setupTrust.backToFirstStepClick();
|
||||
expect(actions.isOnFirstStep()).toBe(true);
|
||||
test('next button should be enabled if trust mode selected', async () => {
|
||||
await actions.setupTrustStep.selectApiKeyTrustMode();
|
||||
expect(actions.setupTrustStep.button.isDisabled()).toBe(false);
|
||||
});
|
||||
|
||||
test('shows only cert based config if API key trust model is not available', async () => {
|
||||
|
@ -227,99 +67,339 @@ describe('Create Remote cluster', () => {
|
|||
}));
|
||||
});
|
||||
|
||||
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);
|
||||
expect(actions.setupTrustStep.apiCardExist()).toBe(false);
|
||||
expect(actions.setupTrustStep.certCardExist()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on prem', () => {
|
||||
describe('Form step', () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
({ actions, component } = await setup(httpSetup));
|
||||
({ actions, component } = await setup(httpSetup, {
|
||||
canUseAPIKeyTrustModel: true,
|
||||
}));
|
||||
});
|
||||
|
||||
component.update();
|
||||
|
||||
actions.nameInput.setValue('remote_cluster_test');
|
||||
await actions.setupTrustStep.selectApiKeyTrustMode();
|
||||
await actions.setupTrustStep.button.click();
|
||||
});
|
||||
|
||||
describe('seeds', () => {
|
||||
test('should only allow alpha-numeric characters and "-" (dash) in the node "host" part', async () => {
|
||||
await actions.saveButton.click(); // display form errors
|
||||
test('should have a toggle to Skip unavailable remote cluster', () => {
|
||||
expect(actions.formStep.skipUnavailableSwitch.exists()).toBe(true);
|
||||
|
||||
// By default it should be set to "true"
|
||||
expect(actions.formStep.skipUnavailableSwitch.isChecked()).toBe(true);
|
||||
|
||||
actions.formStep.skipUnavailableSwitch.toggle();
|
||||
|
||||
expect(actions.formStep.skipUnavailableSwitch.isChecked()).toBe(false);
|
||||
});
|
||||
|
||||
test('back button goes to first step', async () => {
|
||||
await actions.formStep.backButton.click();
|
||||
expect(actions.setupTrustStep.isOnTrustStep()).toBe(true);
|
||||
});
|
||||
|
||||
describe('on prem', () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
({ actions, component } = await setup(httpSetup));
|
||||
});
|
||||
|
||||
component.update();
|
||||
|
||||
actions.formStep.nameInput.setValue('remote_cluster_test');
|
||||
});
|
||||
|
||||
test('should have a toggle to enable "proxy" mode for a remote cluster', () => {
|
||||
expect(actions.formStep.connectionModeSwitch.exists()).toBe(true);
|
||||
|
||||
// By default it should be set to "false"
|
||||
expect(actions.formStep.connectionModeSwitch.isChecked()).toBe(false);
|
||||
|
||||
actions.formStep.connectionModeSwitch.toggle();
|
||||
|
||||
expect(actions.formStep.connectionModeSwitch.isChecked()).toBe(true);
|
||||
});
|
||||
|
||||
test('server name has optional label', () => {
|
||||
actions.formStep.connectionModeSwitch.toggle();
|
||||
expect(actions.formStep.serverNameInput.getLabel()).toBe('Server name (optional)');
|
||||
});
|
||||
|
||||
test('should display errors and disable the next button when clicking "next" without filling the form', async () => {
|
||||
actions.formStep.nameInput.setValue('');
|
||||
expect(actions.globalErrorExists()).toBe(false);
|
||||
expect(actions.formStep.button.isDisabled()).toBe(false);
|
||||
|
||||
await actions.formStep.button.click();
|
||||
|
||||
expect(actions.globalErrorExists()).toBe(true);
|
||||
expect(actions.getErrorMessages()).toEqual([
|
||||
'Name is required.',
|
||||
// seeds input is switched on by default on prem and is required
|
||||
'At least one seed node is required.',
|
||||
]);
|
||||
expect(actions.formStep.button.isDisabled()).toBe(true);
|
||||
});
|
||||
|
||||
test('renders no switch for cloud advanced options', () => {
|
||||
expect(actions.formStep.cloudAdvancedOptionsSwitch.exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('seeds', () => {
|
||||
test('should only allow alpha-numeric characters and "-" (dash) in the node "host" part', async () => {
|
||||
await actions.formStep.button.click(); // display form errors
|
||||
|
||||
const expectInvalidChar = (char: string) => {
|
||||
actions.formStep.seedsInput.setValue(`192.16${char}:3000`);
|
||||
expect(actions.getErrorMessages()).toContain(
|
||||
`Seed node must use host:port format. Example: 127.0.0.1:9400, localhost:9400. Hosts can only consist of letters, numbers, and dashes.`
|
||||
);
|
||||
};
|
||||
|
||||
[...NON_ALPHA_NUMERIC_CHARS, ...ACCENTED_CHARS]
|
||||
.filter(notInArray(['-', '_', ':']))
|
||||
.forEach(expectInvalidChar);
|
||||
});
|
||||
|
||||
test('should require a numeric "port" to be set', async () => {
|
||||
await actions.formStep.button.click();
|
||||
|
||||
actions.formStep.seedsInput.setValue('192.168.1.1');
|
||||
expect(actions.getErrorMessages()).toContain('A port is required.');
|
||||
|
||||
actions.formStep.seedsInput.setValue('192.168.1.1:abc');
|
||||
expect(actions.getErrorMessages()).toContain('A port is required.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('node connections', () => {
|
||||
test('should require a valid number of node connections', async () => {
|
||||
await actions.formStep.button.click();
|
||||
|
||||
actions.formStep.nodeConnectionsInput.setValue(String(MAX_NODE_CONNECTIONS + 1));
|
||||
expect(actions.getErrorMessages()).toContain(
|
||||
`This number must be equal or less than ${MAX_NODE_CONNECTIONS}.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('server name is optional (proxy connection)', () => {
|
||||
actions.formStep.connectionModeSwitch.toggle();
|
||||
actions.formStep.button.click();
|
||||
expect(actions.getErrorMessages()).toEqual(['A proxy address is required.']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on cloud', () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
({ actions, component } = await setup(httpSetup, { isCloudEnabled: true }));
|
||||
});
|
||||
|
||||
component.update();
|
||||
});
|
||||
|
||||
test('TLS server name has optional label', () => {
|
||||
actions.formStep.cloudAdvancedOptionsSwitch.toggle();
|
||||
expect(actions.formStep.tlsServerNameInput.getLabel()).toBe('TLS server name (optional)');
|
||||
});
|
||||
|
||||
test('renders a switch for advanced options', () => {
|
||||
expect(actions.formStep.cloudAdvancedOptionsSwitch.exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('renders no switch between sniff and proxy modes', () => {
|
||||
expect(actions.formStep.connectionModeSwitch.exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('advanced options are initially disabled', () => {
|
||||
expect(actions.formStep.cloudAdvancedOptionsSwitch.isChecked()).toBe(false);
|
||||
});
|
||||
|
||||
test('remote address is required', () => {
|
||||
actions.formStep.button.click();
|
||||
expect(actions.getErrorMessages()).toContain('A remote address is required.');
|
||||
});
|
||||
|
||||
test('should only allow alpha-numeric characters and "-" (dash) in the remote address "host" part', async () => {
|
||||
await actions.formStep.button.click(); // display form errors
|
||||
|
||||
const expectInvalidChar = (char: string) => {
|
||||
actions.seedsInput.setValue(`192.16${char}:3000`);
|
||||
expect(actions.getErrorMessages()).toContain(
|
||||
`Seed node must use host:port format. Example: 127.0.0.1:9400, localhost:9400. Hosts can only consist of letters, numbers, and dashes.`
|
||||
);
|
||||
actions.formStep.cloudRemoteAddressInput.setValue(`192.16${char}:3000`);
|
||||
expect(actions.getErrorMessages()).toContain('Remote address is invalid.');
|
||||
};
|
||||
|
||||
[...NON_ALPHA_NUMERIC_CHARS, ...ACCENTED_CHARS]
|
||||
.filter(notInArray(['-', '_', ':']))
|
||||
.forEach(expectInvalidChar);
|
||||
});
|
||||
|
||||
test('should require a numeric "port" to be set', async () => {
|
||||
await actions.saveButton.click();
|
||||
|
||||
actions.seedsInput.setValue('192.168.1.1');
|
||||
expect(actions.getErrorMessages()).toContain('A port is required.');
|
||||
|
||||
actions.seedsInput.setValue('192.168.1.1:abc');
|
||||
expect(actions.getErrorMessages()).toContain('A port is required.');
|
||||
});
|
||||
});
|
||||
describe('form validation', () => {
|
||||
describe('remote cluster name', () => {
|
||||
test('should not allow spaces', async () => {
|
||||
actions.formStep.nameInput.setValue('with space');
|
||||
|
||||
describe('node connections', () => {
|
||||
test('should require a valid number of node connections', async () => {
|
||||
await actions.saveButton.click();
|
||||
await actions.formStep.button.click();
|
||||
|
||||
actions.nodeConnectionsInput.setValue(String(MAX_NODE_CONNECTIONS + 1));
|
||||
expect(actions.getErrorMessages()).toContain(
|
||||
`This number must be equal or less than ${MAX_NODE_CONNECTIONS}.`
|
||||
);
|
||||
expect(actions.getErrorMessages()).toContain('Spaces are not allowed in the name.');
|
||||
});
|
||||
|
||||
test('should only allow alpha-numeric characters, "-" (dash) and "_" (underscore)', async () => {
|
||||
const expectInvalidChar = (char: string) => {
|
||||
if (char === '-' || char === '_') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
actions.formStep.nameInput.setValue(`with${char}`);
|
||||
|
||||
expect(actions.getErrorMessages()).toContain(
|
||||
`Remove the character ${char} from the name.`
|
||||
);
|
||||
} catch {
|
||||
throw Error(`Char "${char}" expected invalid but was allowed`);
|
||||
}
|
||||
};
|
||||
|
||||
await actions.formStep.button.click(); // display form errors
|
||||
|
||||
[...NON_ALPHA_NUMERIC_CHARS, ...ACCENTED_CHARS].forEach(expectInvalidChar);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('server name is optional (proxy connection)', () => {
|
||||
actions.connectionModeSwitch.toggle();
|
||||
actions.saveButton.click();
|
||||
expect(actions.getErrorMessages()).toEqual(['A proxy address is required.']);
|
||||
describe('proxy address', () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
({ actions, component } = await setup(httpSetup));
|
||||
});
|
||||
|
||||
component.update();
|
||||
|
||||
actions.formStep.connectionModeSwitch.toggle();
|
||||
});
|
||||
|
||||
test('should only allow alpha-numeric characters and "-" (dash) in the proxy address "host" part', async () => {
|
||||
await actions.formStep.button.click(); // display form errors
|
||||
|
||||
const expectInvalidChar = (char: string) => {
|
||||
actions.formStep.proxyAddressInput.setValue(`192.16${char}:3000`);
|
||||
expect(actions.getErrorMessages()).toContain(
|
||||
'Address must use host:port format. Example: 127.0.0.1:9400, localhost:9400. Hosts can only consist of letters, numbers, and dashes.'
|
||||
);
|
||||
};
|
||||
|
||||
[...NON_ALPHA_NUMERIC_CHARS, ...ACCENTED_CHARS]
|
||||
.filter(notInArray(['-', '_', ':']))
|
||||
.forEach(expectInvalidChar);
|
||||
});
|
||||
|
||||
test('should require a numeric "port" to be set', async () => {
|
||||
await actions.formStep.button.click();
|
||||
|
||||
actions.formStep.proxyAddressInput.setValue('192.168.1.1');
|
||||
expect(actions.getErrorMessages()).toContain('A port is required.');
|
||||
|
||||
actions.formStep.proxyAddressInput.setValue('192.168.1.1:abc');
|
||||
expect(actions.getErrorMessages()).toContain('A port is required.');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('on cloud', () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
({ actions, component } = await setup(httpSetup, { isCloudEnabled: true }));
|
||||
describe('Review step', () => {
|
||||
describe('on cloud', () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
({ actions, component } = await setup(httpSetup, { isCloudEnabled: true }));
|
||||
});
|
||||
|
||||
component.update();
|
||||
});
|
||||
|
||||
component.update();
|
||||
test('back button goes to second step', async () => {
|
||||
await actions.setupTrustStep.selectApiKeyTrustMode();
|
||||
await actions.setupTrustStep.button.click();
|
||||
|
||||
await actions.formStep.nameInput.setValue('remote_cluster_apiKey_cloud');
|
||||
await actions.formStep.cloudRemoteAddressInput.setValue('1:1');
|
||||
await actions.formStep.button.click();
|
||||
|
||||
await actions.reviewStep.backButton.click();
|
||||
expect(actions.formStep.isOnFormStep()).toBe(true);
|
||||
});
|
||||
|
||||
test('shows expected documentation when api_key is selected', async () => {
|
||||
await actions.setupTrustStep.selectApiKeyTrustMode();
|
||||
await actions.setupTrustStep.button.click();
|
||||
|
||||
await actions.formStep.nameInput.setValue('remote_cluster_apiKey_cloud');
|
||||
await actions.formStep.cloudRemoteAddressInput.setValue('1:1');
|
||||
await actions.formStep.button.click();
|
||||
|
||||
expect(actions.reviewStep.cloud.apiKeyDocumentationExists()).toBe(true);
|
||||
expect(actions.reviewStep.cloud.certDocumentationExists()).toBe(false);
|
||||
});
|
||||
|
||||
test('shows expected documentation when cert is selected', async () => {
|
||||
await actions.setupTrustStep.selectCertificatesTrustMode();
|
||||
await actions.setupTrustStep.button.click();
|
||||
|
||||
await actions.formStep.nameInput.setValue('remote_cluster_cert_cloud');
|
||||
await actions.formStep.cloudRemoteAddressInput.setValue('1:1');
|
||||
await actions.formStep.button.click();
|
||||
|
||||
expect(actions.reviewStep.cloud.certDocumentationExists()).toBe(true);
|
||||
expect(actions.reviewStep.cloud.apiKeyDocumentationExists()).toBe(false);
|
||||
});
|
||||
});
|
||||
describe('on prem', () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
({ actions, component } = await setup(httpSetup));
|
||||
});
|
||||
|
||||
test('remote address is required', () => {
|
||||
actions.saveButton.click();
|
||||
expect(actions.getErrorMessages()).toContain('A remote address is required.');
|
||||
});
|
||||
component.update();
|
||||
});
|
||||
|
||||
test('should only allow alpha-numeric characters and "-" (dash) in the remote address "host" part', async () => {
|
||||
await actions.saveButton.click(); // display form errors
|
||||
test('shows expected documentation when api_key is selected', async () => {
|
||||
const open = jest.fn();
|
||||
global.open = open;
|
||||
|
||||
const expectInvalidChar = (char: string) => {
|
||||
actions.cloudRemoteAddressInput.setValue(`192.16${char}:3000`);
|
||||
expect(actions.getErrorMessages()).toContain('Remote address is invalid.');
|
||||
};
|
||||
await actions.setupTrustStep.selectApiKeyTrustMode();
|
||||
await actions.setupTrustStep.button.click();
|
||||
|
||||
[...NON_ALPHA_NUMERIC_CHARS, ...ACCENTED_CHARS]
|
||||
.filter(notInArray(['-', '_', ':']))
|
||||
.forEach(expectInvalidChar);
|
||||
await actions.formStep.nameInput.setValue('remote_cluster_apiKey_onPrem');
|
||||
await actions.formStep.seedsInput.setValue('1:1');
|
||||
await actions.formStep.button.click();
|
||||
|
||||
expect(actions.reviewStep.onPrem.step1LinkExists()).toBe(true);
|
||||
expect(actions.reviewStep.onPrem.step1Link()).toBe(onPremPrerequisitesApiKey);
|
||||
|
||||
expect(actions.reviewStep.onPrem.step2LinkExists()).toBe(true);
|
||||
expect(actions.reviewStep.onPrem.step2Link()).toBe(onPremSecurityApiKey);
|
||||
});
|
||||
|
||||
test('shows expected documentation when cert is selected', async () => {
|
||||
const open = jest.fn();
|
||||
global.open = open;
|
||||
|
||||
await actions.setupTrustStep.selectCertificatesTrustMode;
|
||||
await actions.setupTrustStep.button.click();
|
||||
|
||||
await actions.formStep.nameInput.setValue('remote_cluster_cert_onPrem');
|
||||
await actions.formStep.seedsInput.setValue('1:1');
|
||||
await actions.formStep.button.click();
|
||||
|
||||
expect(actions.reviewStep.onPrem.step1LinkExists()).toBe(true);
|
||||
expect(actions.reviewStep.onPrem.step1Link()).toBe(onPremPrerequisitesCert);
|
||||
|
||||
expect(actions.reviewStep.onPrem.step2LinkExists()).toBe(true);
|
||||
expect(actions.reviewStep.onPrem.step2Link()).toBe(onPremSecurityCert);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { registerTestBed, TestBedConfig } from '@kbn/test-jest-helpers';
|
||||
import { HttpSetup } from '@kbn/core/public';
|
||||
|
||||
import { SECURITY_MODEL } from '../../../common/constants';
|
||||
import { Cluster } from '../../../public';
|
||||
import { RemoteClusterEdit } from '../../../public/application/sections';
|
||||
import { createRemoteClustersStore } from '../../../public/application/store';
|
||||
|
@ -20,7 +21,7 @@ export const REMOTE_CLUSTER_EDIT: Cluster = {
|
|||
name: REMOTE_CLUSTER_EDIT_NAME,
|
||||
seeds: ['localhost:9400'],
|
||||
skipUnavailable: true,
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
};
|
||||
|
||||
const testBedConfig: TestBedConfig = {
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
REMOTE_CLUSTER_EDIT_NAME,
|
||||
} from './remote_clusters_edit.helpers';
|
||||
import { Cluster } from '../../../common/lib';
|
||||
import { SECURITY_MODEL } from '../../../common/constants';
|
||||
|
||||
let component: TestBed['component'];
|
||||
let actions: RemoteClustersActions;
|
||||
|
@ -64,14 +65,16 @@ describe('Edit Remote cluster', () => {
|
|||
});
|
||||
|
||||
test('should populate the form fields with the values from the remote cluster loaded', () => {
|
||||
expect(actions.nameInput.getValue()).toBe(REMOTE_CLUSTER_EDIT_NAME);
|
||||
expect(actions.formStep.nameInput.getValue()).toBe(REMOTE_CLUSTER_EDIT_NAME);
|
||||
// seeds input for sniff connection is not shown on Cloud
|
||||
expect(actions.seedsInput.getValue()).toBe(REMOTE_CLUSTER_EDIT.seeds?.join(''));
|
||||
expect(actions.skipUnavailableSwitch.isChecked()).toBe(REMOTE_CLUSTER_EDIT.skipUnavailable);
|
||||
expect(actions.formStep.seedsInput.getValue()).toBe(REMOTE_CLUSTER_EDIT.seeds?.join(''));
|
||||
expect(actions.formStep.skipUnavailableSwitch.isChecked()).toBe(
|
||||
REMOTE_CLUSTER_EDIT.skipUnavailable
|
||||
);
|
||||
});
|
||||
|
||||
test('should disable the form name input', () => {
|
||||
expect(actions.nameInput.isDisabled()).toBe(true);
|
||||
expect(actions.formStep.nameInput.isDisabled()).toBe(true);
|
||||
});
|
||||
|
||||
describe('on cloud', () => {
|
||||
|
@ -83,7 +86,7 @@ describe('Edit Remote cluster', () => {
|
|||
mode: 'proxy',
|
||||
proxyAddress: `${cloudUrl}:${defaultCloudPort}`,
|
||||
serverName: cloudUrl,
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
};
|
||||
httpRequestsMockHelpers.setLoadRemoteClustersResponse([cluster]);
|
||||
|
||||
|
@ -92,9 +95,11 @@ describe('Edit Remote cluster', () => {
|
|||
});
|
||||
component.update();
|
||||
|
||||
expect(actions.cloudRemoteAddressInput.exists()).toBe(true);
|
||||
expect(actions.cloudRemoteAddressInput.getValue()).toBe(`${cloudUrl}:${defaultCloudPort}`);
|
||||
expect(actions.tlsServerNameInput.exists()).toBe(false);
|
||||
expect(actions.formStep.cloudRemoteAddressInput.exists()).toBe(true);
|
||||
expect(actions.formStep.cloudRemoteAddressInput.getValue()).toBe(
|
||||
`${cloudUrl}:${defaultCloudPort}`
|
||||
);
|
||||
expect(actions.formStep.tlsServerNameInput.exists()).toBe(false);
|
||||
});
|
||||
|
||||
test("existing cluster that doesn't have a TLS server name", async () => {
|
||||
|
@ -102,7 +107,7 @@ describe('Edit Remote cluster', () => {
|
|||
name: REMOTE_CLUSTER_EDIT_NAME,
|
||||
mode: 'proxy',
|
||||
proxyAddress: `${cloudUrl}:9500`,
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
};
|
||||
httpRequestsMockHelpers.setLoadRemoteClustersResponse([cluster]);
|
||||
|
||||
|
@ -111,9 +116,9 @@ describe('Edit Remote cluster', () => {
|
|||
});
|
||||
component.update();
|
||||
|
||||
expect(actions.cloudRemoteAddressInput.exists()).toBe(true);
|
||||
expect(actions.cloudRemoteAddressInput.getValue()).toBe(`${cloudUrl}:9500`);
|
||||
expect(actions.tlsServerNameInput.exists()).toBe(true);
|
||||
expect(actions.formStep.cloudRemoteAddressInput.exists()).toBe(true);
|
||||
expect(actions.formStep.cloudRemoteAddressInput.getValue()).toBe(`${cloudUrl}:9500`);
|
||||
expect(actions.formStep.tlsServerNameInput.exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('existing cluster that has remote address different from TLS server name)', async () => {
|
||||
|
@ -122,7 +127,7 @@ describe('Edit Remote cluster', () => {
|
|||
mode: 'proxy',
|
||||
proxyAddress: `${cloudUrl}:${defaultCloudPort}`,
|
||||
serverName: 'another-value',
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
};
|
||||
httpRequestsMockHelpers.setLoadRemoteClustersResponse([cluster]);
|
||||
|
||||
|
@ -131,9 +136,11 @@ describe('Edit Remote cluster', () => {
|
|||
});
|
||||
component.update();
|
||||
|
||||
expect(actions.cloudRemoteAddressInput.exists()).toBe(true);
|
||||
expect(actions.cloudRemoteAddressInput.getValue()).toBe(`${cloudUrl}:${defaultCloudPort}`);
|
||||
expect(actions.tlsServerNameInput.exists()).toBe(true);
|
||||
expect(actions.formStep.cloudRemoteAddressInput.exists()).toBe(true);
|
||||
expect(actions.formStep.cloudRemoteAddressInput.getValue()).toBe(
|
||||
`${cloudUrl}:${defaultCloudPort}`
|
||||
);
|
||||
expect(actions.formStep.tlsServerNameInput.exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,72 +13,108 @@ export interface RemoteClustersActions {
|
|||
exists: () => boolean;
|
||||
text: () => string;
|
||||
};
|
||||
nameInput: {
|
||||
setValue: (name: string) => void;
|
||||
getValue: () => string;
|
||||
isDisabled: () => boolean;
|
||||
formStep: {
|
||||
nameInput: {
|
||||
setValue: (name: string) => void;
|
||||
getValue: () => string;
|
||||
isDisabled: () => boolean;
|
||||
};
|
||||
skipUnavailableSwitch: {
|
||||
exists: () => boolean;
|
||||
toggle: () => void;
|
||||
isChecked: () => boolean;
|
||||
};
|
||||
connectionModeSwitch: {
|
||||
exists: () => boolean;
|
||||
toggle: () => void;
|
||||
isChecked: () => boolean;
|
||||
};
|
||||
cloudAdvancedOptionsSwitch: {
|
||||
toggle: () => void;
|
||||
exists: () => boolean;
|
||||
isChecked: () => boolean;
|
||||
};
|
||||
cloudRemoteAddressInput: {
|
||||
exists: () => boolean;
|
||||
getValue: () => string;
|
||||
setValue: (remoteAddress: string) => void;
|
||||
};
|
||||
seedsInput: {
|
||||
setValue: (seed: string) => void;
|
||||
getValue: () => string;
|
||||
};
|
||||
nodeConnectionsInput: {
|
||||
setValue: (connections: string) => void;
|
||||
};
|
||||
proxyAddressInput: {
|
||||
setValue: (proxyAddress: string) => void;
|
||||
exists: () => boolean;
|
||||
};
|
||||
serverNameInput: {
|
||||
getLabel: () => string;
|
||||
exists: () => boolean;
|
||||
};
|
||||
tlsServerNameInput: {
|
||||
getLabel: () => string;
|
||||
exists: () => boolean;
|
||||
};
|
||||
button: {
|
||||
click: () => void;
|
||||
isDisabled: () => boolean;
|
||||
};
|
||||
backButton: {
|
||||
click: () => void;
|
||||
};
|
||||
isOnFormStep: () => boolean;
|
||||
};
|
||||
skipUnavailableSwitch: {
|
||||
exists: () => boolean;
|
||||
toggle: () => void;
|
||||
isChecked: () => boolean;
|
||||
};
|
||||
connectionModeSwitch: {
|
||||
exists: () => boolean;
|
||||
toggle: () => void;
|
||||
isChecked: () => boolean;
|
||||
};
|
||||
cloudAdvancedOptionsSwitch: {
|
||||
toggle: () => void;
|
||||
exists: () => boolean;
|
||||
isChecked: () => boolean;
|
||||
};
|
||||
cloudRemoteAddressInput: {
|
||||
exists: () => boolean;
|
||||
getValue: () => string;
|
||||
setValue: (remoteAddress: string) => void;
|
||||
};
|
||||
seedsInput: {
|
||||
setValue: (seed: string) => void;
|
||||
getValue: () => string;
|
||||
};
|
||||
nodeConnectionsInput: {
|
||||
setValue: (connections: string) => void;
|
||||
};
|
||||
proxyAddressInput: {
|
||||
setValue: (proxyAddress: string) => void;
|
||||
exists: () => boolean;
|
||||
};
|
||||
serverNameInput: {
|
||||
getLabel: () => string;
|
||||
exists: () => boolean;
|
||||
};
|
||||
tlsServerNameInput: {
|
||||
getLabel: () => string;
|
||||
exists: () => boolean;
|
||||
};
|
||||
isOnFirstStep: () => boolean;
|
||||
saveButton: {
|
||||
click: () => void;
|
||||
isDisabled: () => boolean;
|
||||
};
|
||||
setupTrust: {
|
||||
isSubmitInConfirmDisabled: () => boolean;
|
||||
toggleConfirmSwitch: () => void;
|
||||
setupTrustConfirmClick: () => void;
|
||||
backToFirstStepClick: () => void;
|
||||
|
||||
setupTrustStep: {
|
||||
apiCardExist: () => boolean;
|
||||
certCardExist: () => boolean;
|
||||
apiCardDocsExist: () => boolean;
|
||||
certCardDocsExist: () => boolean;
|
||||
selectApiKeyTrustMode: () => void;
|
||||
selectCertificatesTrustMode: () => void;
|
||||
button: {
|
||||
click: () => void;
|
||||
isDisabled: () => boolean;
|
||||
};
|
||||
isOnTrustStep: () => boolean;
|
||||
};
|
||||
|
||||
reviewStep: {
|
||||
onPrem: {
|
||||
exists: () => boolean;
|
||||
step1LinkExists: () => boolean;
|
||||
step2LinkExists: () => boolean;
|
||||
step1Link: () => string;
|
||||
step2Link: () => string;
|
||||
};
|
||||
cloud: {
|
||||
apiKeyDocumentationExists: () => boolean;
|
||||
certDocumentationExists: () => boolean;
|
||||
};
|
||||
clickAddCluster: () => void;
|
||||
errorBannerExists: () => boolean;
|
||||
backButton: {
|
||||
click: () => void;
|
||||
};
|
||||
};
|
||||
|
||||
getErrorMessages: () => string[];
|
||||
globalErrorExists: () => boolean;
|
||||
}
|
||||
export const createRemoteClustersActions = (testBed: TestBed): RemoteClustersActions => {
|
||||
const { form, exists, find, component } = testBed;
|
||||
|
||||
const click = (selector: string) => {
|
||||
act(() => {
|
||||
find(selector).simulate('click');
|
||||
});
|
||||
|
||||
component.update();
|
||||
};
|
||||
|
||||
const docsButtonExists = () => exists('remoteClusterDocsButton');
|
||||
|
||||
const createPageTitleActions = () => {
|
||||
const pageTitleSelector = 'remoteClusterPageTitle';
|
||||
return {
|
||||
|
@ -88,203 +124,222 @@ export const createRemoteClustersActions = (testBed: TestBed): RemoteClustersAct
|
|||
},
|
||||
};
|
||||
};
|
||||
const createNameInputActions = () => {
|
||||
const nameInputSelector = 'remoteClusterFormNameInput';
|
||||
return {
|
||||
nameInput: {
|
||||
setValue: (name: string) => form.setInputValue(nameInputSelector, name),
|
||||
getValue: () => find(nameInputSelector).props().value,
|
||||
isDisabled: () => find(nameInputSelector).props().disabled,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createSkipUnavailableActions = () => {
|
||||
const skipUnavailableToggleSelector = 'remoteClusterFormSkipUnavailableFormToggle';
|
||||
return {
|
||||
skipUnavailableSwitch: {
|
||||
exists: () => exists(skipUnavailableToggleSelector),
|
||||
toggle: () => {
|
||||
act(() => {
|
||||
form.toggleEuiSwitch(skipUnavailableToggleSelector);
|
||||
});
|
||||
component.update();
|
||||
const formStepActions = () => {
|
||||
const createNameInputActions = () => {
|
||||
const nameInputSelector = 'remoteClusterFormNameInput';
|
||||
return {
|
||||
nameInput: {
|
||||
setValue: (name: string) => form.setInputValue(nameInputSelector, name),
|
||||
getValue: () => find(nameInputSelector).props().value,
|
||||
isDisabled: () => find(nameInputSelector).props().disabled,
|
||||
},
|
||||
isChecked: () => find(skipUnavailableToggleSelector).props()['aria-checked'],
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const createConnectionModeActions = () => {
|
||||
const connectionModeToggleSelector = 'remoteClusterFormConnectionModeToggle';
|
||||
return {
|
||||
connectionModeSwitch: {
|
||||
exists: () => exists(connectionModeToggleSelector),
|
||||
toggle: () => {
|
||||
act(() => {
|
||||
form.toggleEuiSwitch(connectionModeToggleSelector);
|
||||
});
|
||||
component.update();
|
||||
const createSkipUnavailableActions = () => {
|
||||
const skipUnavailableToggleSelector = 'remoteClusterFormSkipUnavailableFormToggle';
|
||||
return {
|
||||
skipUnavailableSwitch: {
|
||||
exists: () => exists(skipUnavailableToggleSelector),
|
||||
toggle: () => {
|
||||
act(() => {
|
||||
form.toggleEuiSwitch(skipUnavailableToggleSelector);
|
||||
});
|
||||
component.update();
|
||||
},
|
||||
isChecked: () => find(skipUnavailableToggleSelector).props()['aria-checked'],
|
||||
},
|
||||
isChecked: () => find(connectionModeToggleSelector).props()['aria-checked'],
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const createCloudAdvancedOptionsSwitchActions = () => {
|
||||
const cloudUrlSelector = 'remoteClusterFormCloudAdvancedOptionsToggle';
|
||||
return {
|
||||
cloudAdvancedOptionsSwitch: {
|
||||
exists: () => exists(cloudUrlSelector),
|
||||
toggle: () => {
|
||||
act(() => {
|
||||
form.toggleEuiSwitch(cloudUrlSelector);
|
||||
});
|
||||
component.update();
|
||||
const createConnectionModeActions = () => {
|
||||
const connectionModeToggleSelector = 'remoteClusterFormConnectionModeToggle';
|
||||
return {
|
||||
connectionModeSwitch: {
|
||||
exists: () => exists(connectionModeToggleSelector),
|
||||
toggle: () => {
|
||||
act(() => {
|
||||
form.toggleEuiSwitch(connectionModeToggleSelector);
|
||||
});
|
||||
component.update();
|
||||
},
|
||||
isChecked: () => find(connectionModeToggleSelector).props()['aria-checked'],
|
||||
},
|
||||
isChecked: () => find(cloudUrlSelector).props()['aria-checked'],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createSeedsInputActions = () => {
|
||||
const seedsInputSelector = 'remoteClusterFormSeedsInput';
|
||||
return {
|
||||
seedsInput: {
|
||||
setValue: (seed: string) => form.setComboBoxValue(seedsInputSelector, seed),
|
||||
getValue: () => find(seedsInputSelector).text(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createNodeConnectionsInputActions = () => {
|
||||
const nodeConnectionsInputSelector = 'remoteClusterFormNodeConnectionsInput';
|
||||
return {
|
||||
nodeConnectionsInput: {
|
||||
setValue: (connections: string) =>
|
||||
form.setInputValue(nodeConnectionsInputSelector, connections),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createProxyAddressActions = () => {
|
||||
const proxyAddressSelector = 'remoteClusterFormProxyAddressInput';
|
||||
return {
|
||||
proxyAddressInput: {
|
||||
setValue: (proxyAddress: string) => form.setInputValue(proxyAddressSelector, proxyAddress),
|
||||
exists: () => exists(proxyAddressSelector),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createSetupTrustActions = () => {
|
||||
const click = () => {
|
||||
act(() => {
|
||||
find('remoteClusterFormSaveButton').simulate('click');
|
||||
});
|
||||
|
||||
component.update();
|
||||
};
|
||||
const isDisabled = () => find('remoteClusterFormSaveButton').props().disabled;
|
||||
|
||||
const setupTrustConfirmClick = () => {
|
||||
act(() => {
|
||||
find('setupTrustDoneButton').simulate('click');
|
||||
});
|
||||
|
||||
component.update();
|
||||
};
|
||||
};
|
||||
|
||||
const backToFirstStepClick = () => {
|
||||
act(() => {
|
||||
find('setupTrustBackButton').simulate('click');
|
||||
});
|
||||
|
||||
component.update();
|
||||
const createCloudAdvancedOptionsSwitchActions = () => {
|
||||
const cloudUrlSelector = 'remoteClusterFormCloudAdvancedOptionsToggle';
|
||||
return {
|
||||
cloudAdvancedOptionsSwitch: {
|
||||
exists: () => exists(cloudUrlSelector),
|
||||
toggle: () => {
|
||||
act(() => {
|
||||
form.toggleEuiSwitch(cloudUrlSelector);
|
||||
});
|
||||
component.update();
|
||||
},
|
||||
isChecked: () => find(cloudUrlSelector).props()['aria-checked'],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
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 createSeedsInputActions = () => {
|
||||
const seedsInputSelector = 'remoteClusterFormSeedsInput';
|
||||
return {
|
||||
seedsInput: {
|
||||
setValue: (seed: string) => form.setComboBoxValue(seedsInputSelector, seed),
|
||||
getValue: () => find(seedsInputSelector).text(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const isSubmitInConfirmDisabled = () => find('remoteClusterTrustSubmitButton').props().disabled;
|
||||
const createNodeConnectionsInputActions = () => {
|
||||
const nodeConnectionsInputSelector = 'remoteClusterFormNodeConnectionsInput';
|
||||
return {
|
||||
nodeConnectionsInput: {
|
||||
setValue: (connections: string) =>
|
||||
form.setInputValue(nodeConnectionsInputSelector, connections),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const apiCardExist = () => exists('setupTrustApiKeyCard');
|
||||
const certCardExist = () => exists('setupTrustCertCard');
|
||||
const apiCardDocsExist = () => exists('setupTrustApiKeyCardDocs');
|
||||
const certCardDocsExist = () => exists('setupTrustCertCardDocs');
|
||||
const createProxyAddressActions = () => {
|
||||
const proxyAddressSelector = 'remoteClusterFormProxyAddressInput';
|
||||
return {
|
||||
proxyAddressInput: {
|
||||
setValue: (proxyAddress: string) =>
|
||||
form.setInputValue(proxyAddressSelector, proxyAddress),
|
||||
exists: () => exists(proxyAddressSelector),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const formButtonsActions = () => {
|
||||
const formButtonSelector = 'remoteClusterFormNextButton';
|
||||
return {
|
||||
button: {
|
||||
click: () => click(formButtonSelector),
|
||||
isDisabled: () => find(formButtonSelector).props().disabled,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const formBackButtonActions = () => {
|
||||
return {
|
||||
backButton: {
|
||||
click: () => click('remoteClusterFormBackButton'),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const isOnFormStepActions = () => {
|
||||
return { isOnFormStep: () => exists('remoteClusterFormNextButton') };
|
||||
};
|
||||
|
||||
const createServerNameActions = () => {
|
||||
const serverNameSelector = 'remoteClusterFormServerNameFormRow';
|
||||
return {
|
||||
serverNameInput: {
|
||||
getLabel: () => find('remoteClusterFormServerNameFormRow').find('label').text(),
|
||||
exists: () => exists(serverNameSelector),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createTlsServerNameActions = () => {
|
||||
const serverNameSelector = 'remoteClusterFormTLSServerNameFormRow';
|
||||
return {
|
||||
tlsServerNameInput: {
|
||||
getLabel: () => find(serverNameSelector).find('label').text(),
|
||||
exists: () => exists(serverNameSelector),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createCloudRemoteAddressInputActions = () => {
|
||||
const cloudUrlInputSelector = 'remoteClusterFormRemoteAddressInput';
|
||||
return {
|
||||
cloudRemoteAddressInput: {
|
||||
exists: () => exists(cloudUrlInputSelector),
|
||||
getValue: () => find(cloudUrlInputSelector).props().value,
|
||||
setValue: (remoteAddress: string) =>
|
||||
form.setInputValue(cloudUrlInputSelector, remoteAddress),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
isOnFirstStep,
|
||||
saveButton: { click, isDisabled },
|
||||
setupTrust: {
|
||||
setupTrustConfirmClick,
|
||||
isSubmitInConfirmDisabled,
|
||||
toggleConfirmSwitch,
|
||||
apiCardExist,
|
||||
certCardExist,
|
||||
apiCardDocsExist,
|
||||
certCardDocsExist,
|
||||
backToFirstStepClick,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createServerNameActions = () => {
|
||||
const serverNameSelector = 'remoteClusterFormServerNameFormRow';
|
||||
return {
|
||||
serverNameInput: {
|
||||
getLabel: () => find('remoteClusterFormServerNameFormRow').find('label').text(),
|
||||
exists: () => exists(serverNameSelector),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createTlsServerNameActions = () => {
|
||||
const serverNameSelector = 'remoteClusterFormTLSServerNameFormRow';
|
||||
return {
|
||||
tlsServerNameInput: {
|
||||
getLabel: () => find(serverNameSelector).find('label').text(),
|
||||
exists: () => exists(serverNameSelector),
|
||||
formStep: {
|
||||
...createNameInputActions(),
|
||||
...createSkipUnavailableActions(),
|
||||
...createConnectionModeActions(),
|
||||
...createCloudAdvancedOptionsSwitchActions(),
|
||||
...createSeedsInputActions(),
|
||||
...createNodeConnectionsInputActions(),
|
||||
...createProxyAddressActions(),
|
||||
...formButtonsActions(),
|
||||
...formBackButtonActions(),
|
||||
...isOnFormStepActions(),
|
||||
...createServerNameActions(),
|
||||
...createTlsServerNameActions(),
|
||||
...createCloudRemoteAddressInputActions(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const globalErrorExists = () => exists('remoteClusterFormGlobalError');
|
||||
|
||||
const createCloudRemoteAddressInputActions = () => {
|
||||
const cloudUrlInputSelector = 'remoteClusterFormRemoteAddressInput';
|
||||
const setupTrustStepActions = () => {
|
||||
const trustButtonSelector = 'remoteClusterTrustNextButton';
|
||||
return {
|
||||
cloudRemoteAddressInput: {
|
||||
exists: () => exists(cloudUrlInputSelector),
|
||||
getValue: () => find(cloudUrlInputSelector).props().value,
|
||||
setValue: (remoteAddress: string) =>
|
||||
form.setInputValue(cloudUrlInputSelector, remoteAddress),
|
||||
setupTrustStep: {
|
||||
apiCardExist: () => exists('setupTrustApiMode'),
|
||||
certCardExist: () => exists('setupTrustCertMode'),
|
||||
selectApiKeyTrustMode: () => click('setupTrustApiMode'),
|
||||
selectCertificatesTrustMode: () => click('setupTrustCertMode'),
|
||||
button: {
|
||||
click: () => click(trustButtonSelector),
|
||||
isDisabled: () => find(trustButtonSelector).props().disabled,
|
||||
},
|
||||
isOnTrustStep: () => exists(trustButtonSelector),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const reviewStepActions = () => {
|
||||
const onPremReviewStepsSelector = 'remoteClusterReviewOnPremSteps';
|
||||
const onPremStep1Selector = 'remoteClusterReviewOnPremStep1';
|
||||
const onPremStep2Selector = 'remoteClusterReviewOnPremStep2';
|
||||
return {
|
||||
reviewStep: {
|
||||
onPrem: {
|
||||
exists: () => exists(onPremReviewStepsSelector),
|
||||
step1LinkExists: () => exists(onPremStep1Selector),
|
||||
step2LinkExists: () => exists(onPremStep2Selector),
|
||||
step1Link: () => find(onPremStep1Selector).props().href,
|
||||
step2Link: () => find(onPremStep2Selector).props().href,
|
||||
},
|
||||
cloud: {
|
||||
apiKeyDocumentationExists: () => exists('cloudApiKeySteps'),
|
||||
certDocumentationExists: () => exists('cloudCertDocumentation'),
|
||||
},
|
||||
clickAddCluster: () => click('remoteClusterReviewtNextButton'),
|
||||
errorBannerExists: () => exists('saveErrorBanner'),
|
||||
backButton: {
|
||||
click: () => click('remoteClusterReviewtBackButton'),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
docsButtonExists,
|
||||
...createPageTitleActions(),
|
||||
...createNameInputActions(),
|
||||
...createSkipUnavailableActions(),
|
||||
...createConnectionModeActions(),
|
||||
...createCloudAdvancedOptionsSwitchActions(),
|
||||
...createSeedsInputActions(),
|
||||
...createNodeConnectionsInputActions(),
|
||||
...createCloudRemoteAddressInputActions(),
|
||||
...createProxyAddressActions(),
|
||||
...createServerNameActions(),
|
||||
...createTlsServerNameActions(),
|
||||
...createSetupTrustActions(),
|
||||
...formStepActions(),
|
||||
...setupTrustStepActions(),
|
||||
...reviewStepActions(),
|
||||
getErrorMessages: form.getErrorsMessages,
|
||||
globalErrorExists,
|
||||
};
|
||||
|
|
|
@ -34,6 +34,7 @@ export const WithAppDependencies =
|
|||
context={{
|
||||
isCloudEnabled: !!isCloudEnabled,
|
||||
cloudBaseUrl: 'test.com',
|
||||
cloudDeploymentUrl: 'deployment.com',
|
||||
executionContext: executionContextServiceMock.createStartContract(),
|
||||
canUseAPIKeyTrustModel: true,
|
||||
...overrides,
|
||||
|
|
|
@ -28,13 +28,13 @@ export const SNIFF_MODE = 'sniff';
|
|||
export const PROXY_MODE = 'proxy';
|
||||
|
||||
export const getSecurityModel = (type: string) => {
|
||||
if (type === 'certificate') {
|
||||
if (type === SECURITY_MODEL.CERTIFICATE) {
|
||||
return i18n.translate('xpack.remoteClusters.securityModelCert', {
|
||||
defaultMessage: 'Certificate',
|
||||
});
|
||||
}
|
||||
|
||||
if (type === 'api_key') {
|
||||
if (type === SECURITY_MODEL.API) {
|
||||
return i18n.translate('xpack.remoteClusters.securityModelApiKey', {
|
||||
defaultMessage: 'API key',
|
||||
});
|
||||
|
@ -45,3 +45,8 @@ export const getSecurityModel = (type: string) => {
|
|||
|
||||
// Hardcoded limit of maximum node connections allowed
|
||||
export const MAX_NODE_CONNECTIONS = 2 ** 31 - 1; // 2147483647
|
||||
|
||||
export enum SECURITY_MODEL {
|
||||
API = 'api_key',
|
||||
CERTIFICATE = 'certificate',
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SECURITY_MODEL } from '../constants';
|
||||
import { deserializeCluster, serializeCluster } from './cluster_serialization';
|
||||
|
||||
describe('cluster_serialization', () => {
|
||||
|
@ -40,7 +41,7 @@ describe('cluster_serialization', () => {
|
|||
skipUnavailable: false,
|
||||
transportPingSchedule: '-1',
|
||||
transportCompress: false,
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -72,7 +73,7 @@ describe('cluster_serialization', () => {
|
|||
transportPingSchedule: '-1',
|
||||
transportCompress: false,
|
||||
serverName: 'my_server_name',
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -94,7 +95,7 @@ describe('cluster_serialization', () => {
|
|||
maxConnectionsPerCluster: 3,
|
||||
initialConnectTimeout: '30s',
|
||||
skipUnavailable: false,
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -128,7 +129,7 @@ describe('cluster_serialization', () => {
|
|||
skipUnavailable: false,
|
||||
transportPingSchedule: '-1',
|
||||
transportCompress: false,
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -164,7 +165,7 @@ describe('cluster_serialization', () => {
|
|||
transportPingSchedule: '-1',
|
||||
transportCompress: false,
|
||||
serverName: 'localhost',
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -186,7 +187,7 @@ describe('cluster_serialization', () => {
|
|||
connectedNodesCount: 1,
|
||||
initialConnectTimeout: '30s',
|
||||
transportCompress: false,
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -217,7 +218,7 @@ describe('cluster_serialization', () => {
|
|||
skipUnavailable: false,
|
||||
transportPingSchedule: '-1',
|
||||
transportCompress: false,
|
||||
securityModel: 'api_key',
|
||||
securityModel: SECURITY_MODEL.API,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { PROXY_MODE } from '../constants';
|
||||
import { PROXY_MODE, SECURITY_MODEL } from '../constants';
|
||||
|
||||
// Values returned from ES GET /_remote/info
|
||||
/**
|
||||
|
@ -113,7 +113,7 @@ export function deserializeCluster(
|
|||
proxySocketConnections,
|
||||
connectedSocketsCount,
|
||||
serverName,
|
||||
securityModel: clusterCredentials ? 'api_key' : 'certificate',
|
||||
securityModel: clusterCredentials ? SECURITY_MODEL.API : SECURITY_MODEL.CERTIFICATE,
|
||||
};
|
||||
|
||||
if (transport) {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { getRandomString } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { SNIFF_MODE } from '../common/constants';
|
||||
import { SECURITY_MODEL, SNIFF_MODE } from '../common/constants';
|
||||
|
||||
export const getRemoteClusterMock = ({
|
||||
name = getRandomString(),
|
||||
|
@ -19,7 +19,7 @@ export const getRemoteClusterMock = ({
|
|||
mode = SNIFF_MODE,
|
||||
proxyAddress,
|
||||
hasDeprecatedProxySetting = false,
|
||||
securityModel = 'certificate',
|
||||
securityModel = SECURITY_MODEL.CERTIFICATE,
|
||||
} = {}) => ({
|
||||
name,
|
||||
seeds,
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
// Remote clusters plugin hacks
|
||||
|
||||
// Prefix all styles with "remoteClusters" to avoid conflicts.
|
||||
// Examples
|
||||
// remoteClustersChart
|
||||
// remoteClustersChart__legend
|
||||
// remoteClustersChart__legend--small
|
||||
// remoteClustersChart__legend-isLoading
|
||||
|
||||
/**
|
||||
* 1. Override EuiFormRow styles. Otherwise the switch will jump around when toggled on and off,
|
||||
* as the 'Reset to defaults' link is added to and removed from the DOM.
|
||||
* 2. Fix the positioning.
|
||||
*/
|
||||
.remoteClusterSkipIfUnavailableSwitch {
|
||||
justify-content: flex-start !important; /* 1 */
|
||||
padding-top: $euiSizeS !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Prevent inherited flexbox layout from compressing this element on IE.
|
||||
*/
|
||||
.remoteClustersConnectionStatus__message {
|
||||
flex-basis: auto !important; /* 1 */
|
||||
}
|
|
@ -11,6 +11,7 @@ import { ExecutionContextStart } from '@kbn/core/public';
|
|||
export interface Context {
|
||||
isCloudEnabled: boolean;
|
||||
cloudBaseUrl: string;
|
||||
cloudDeploymentUrl: string;
|
||||
executionContext: ExecutionContextStart;
|
||||
canUseAPIKeyTrustModel: boolean;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ export declare const renderApp: (
|
|||
appDependencies: {
|
||||
isCloudEnabled: boolean;
|
||||
cloudBaseUrl: string;
|
||||
cloudDeploymentUrl: string;
|
||||
executionContext: ExecutionContextStart;
|
||||
canUseAPIKeyTrustModel: boolean;
|
||||
},
|
||||
|
|
|
@ -14,8 +14,6 @@ import { App } from './app';
|
|||
import { remoteClustersStore } from './store';
|
||||
import { AppContextProvider } from './app_context';
|
||||
|
||||
import './_hacks.scss';
|
||||
|
||||
const AppWithExecutionContext = ({ history, executionContext }) => {
|
||||
useExecutionContext(executionContext, {
|
||||
type: 'application',
|
||||
|
|
|
@ -4,14 +4,11 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
export function ConfiguredByNodeWarning() {
|
||||
export const ConfiguredByNodeWarning: React.FC = () => {
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={
|
||||
|
@ -26,4 +23,4 @@ export function ConfiguredByNodeWarning() {
|
|||
data-test-subj="remoteClusterConfiguredByNodeWarning"
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
|
@ -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, { ReactNode, useCallback, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '../../../../../shared_imports';
|
||||
import { ClusterPayload } from '../../../../../../common/lib';
|
||||
import { RequestFlyout } from './request_flyout';
|
||||
|
||||
interface Props {
|
||||
showRequest: boolean;
|
||||
disabled?: boolean;
|
||||
isSaving?: boolean;
|
||||
handleNext: () => void;
|
||||
onBack?: () => void;
|
||||
confirmFormText: ReactNode;
|
||||
backFormText?: ReactNode;
|
||||
cluster?: ClusterPayload;
|
||||
nextButtonTestSubj: string;
|
||||
backButtonTestSubj?: string;
|
||||
}
|
||||
|
||||
export const ActionButtons: React.FC<Props> = ({
|
||||
showRequest,
|
||||
handleNext,
|
||||
disabled,
|
||||
isSaving,
|
||||
onBack,
|
||||
confirmFormText,
|
||||
backFormText,
|
||||
cluster,
|
||||
nextButtonTestSubj,
|
||||
backButtonTestSubj,
|
||||
}) => {
|
||||
const [isRequestVisible, setIsRequestVisible] = useState(false);
|
||||
const toggleRequest = useCallback(() => {
|
||||
setIsRequestVisible((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup wrap justifyContent="center">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill
|
||||
onClick={handleNext}
|
||||
disabled={disabled}
|
||||
isLoading={isSaving}
|
||||
data-test-subj={nextButtonTestSubj}
|
||||
>
|
||||
{confirmFormText}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{onBack && (
|
||||
<EuiButtonEmpty color="primary" onClick={onBack} data-test-subj={backButtonTestSubj}>
|
||||
{backFormText}
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{showRequest && (
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={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>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{isRequestVisible && cluster ? (
|
||||
<RequestFlyout cluster={cluster} close={() => setIsRequestVisible(false)} />
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 { ActionButtons } from './action_buttons';
|
||||
export { RequestFlyout } from './request_flyout';
|
||||
export { SaveError } from './save_error';
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { RequestError } from '../../../../../types';
|
||||
|
||||
interface Props {
|
||||
saveError: RequestError;
|
||||
}
|
||||
export const SaveError: React.FC<Props> = ({ saveError }) => {
|
||||
const { message, cause } = saveError;
|
||||
const renderErrorBody = () => {
|
||||
if (!cause || !Array.isArray(cause)) return null;
|
||||
return cause.length === 1 ? (
|
||||
<p>{cause[0]}</p>
|
||||
) : (
|
||||
<ul>
|
||||
{cause.map((causeValue, index) => (
|
||||
<li key={index}>{causeValue}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut title={message} color="danger" iconType="error" data-test-subj="saveErrorBanner">
|
||||
{renderErrorBody()}
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -8,7 +8,7 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { EuiDescribedFormGroup, EuiTitle, EuiFormRow, EuiSwitch } from '@elastic/eui';
|
||||
import { EuiDescribedFormGroup, EuiTitle, EuiFormRow, EuiSwitch, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { SNIFF_MODE, PROXY_MODE } from '../../../../../../../common/constants';
|
||||
import { useAppContext } from '../../../../../app_context';
|
||||
|
@ -52,12 +52,13 @@ export const ConnectionMode: FunctionComponent<Props> = (props) => {
|
|||
id="xpack.remoteClusters.remoteClusterForm.sectionModeDescription"
|
||||
defaultMessage="Use seed nodes by default, or switch to proxy mode."
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFormRow fullWidth>
|
||||
<EuiSwitch
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.fieldModeLabel"
|
||||
defaultMessage="Use proxy mode"
|
||||
defaultMessage="Manually enter proxy address and server name"
|
||||
/>
|
||||
}
|
||||
checked={mode === PROXY_MODE}
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
EuiLink,
|
||||
EuiFieldNumber,
|
||||
EuiCode,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -53,10 +54,13 @@ export const ConnectionModeCloud: FunctionComponent<Props> = (props) => {
|
|||
}
|
||||
description={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionModeCloudDescription"
|
||||
defaultMessage="Configure how to connect to the remote cluster."
|
||||
/>
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionModeCloudDescription"
|
||||
defaultMessage="Configure how to connect to the remote cluster."
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFormRow fullWidth>
|
||||
<EuiSwitch
|
||||
label={
|
||||
|
@ -99,9 +103,9 @@ export const ConnectionModeCloud: FunctionComponent<Props> = (props) => {
|
|||
<EuiFieldText
|
||||
value={cloudRemoteAddress}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.remoteClusters.remoteClusterForm.fieldProxyAddressPlaceholder',
|
||||
'xpack.remoteClusters.remoteClusterForm.fieldRemoteAddressPlaceholder',
|
||||
{
|
||||
defaultMessage: 'host:port',
|
||||
defaultMessage: 'hostname:port',
|
||||
}
|
||||
)}
|
||||
onChange={(e) => onFieldsChange({ cloudRemoteAddress: e.target.value })}
|
||||
|
|
|
@ -5,23 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { useState, useContext, useCallback, useEffect } from 'react';
|
||||
import { merge } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { css } from '@emotion/react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiDescribedFormGroup,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiLink,
|
||||
EuiLoadingLogo,
|
||||
EuiOverlayMask,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiTitle,
|
||||
|
@ -29,16 +24,14 @@ import {
|
|||
EuiScreenReaderOnly,
|
||||
htmlIdGenerator,
|
||||
EuiSwitchEvent,
|
||||
useEuiTheme,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { ReactNode } from 'react-markdown';
|
||||
import { Cluster, ClusterPayload } from '../../../../../../common/lib';
|
||||
import { SNIFF_MODE, PROXY_MODE } from '../../../../../../common/constants';
|
||||
|
||||
import { AppContext, Context } from '../../../../app_context';
|
||||
|
||||
import { AppContext } from '../../../../app_context';
|
||||
import { skippingDisconnectedClustersUrl } from '../../../../services/documentation';
|
||||
|
||||
import { RequestFlyout } from '../components/request_flyout';
|
||||
import { ConnectionMode } from './components';
|
||||
import {
|
||||
ClusterErrors,
|
||||
|
@ -46,7 +39,7 @@ import {
|
|||
validateCluster,
|
||||
isCloudAdvancedOptionsEnabled,
|
||||
} from './validators';
|
||||
|
||||
import { ActionButtons, SaveError } from '../components';
|
||||
const defaultClusterValues: ClusterPayload = {
|
||||
name: '',
|
||||
seeds: [],
|
||||
|
@ -56,148 +49,76 @@ const defaultClusterValues: ClusterPayload = {
|
|||
proxySocketConnections: 18,
|
||||
serverName: '',
|
||||
};
|
||||
|
||||
const ERROR_TITLE_ID = 'removeClustersErrorTitle';
|
||||
const ERROR_LIST_ID = 'removeClustersErrorList';
|
||||
|
||||
interface Props {
|
||||
save: (cluster: ClusterPayload) => void;
|
||||
cancel?: () => void;
|
||||
confirmFormAction: (cluster: ClusterPayload) => void;
|
||||
onBack?: () => void;
|
||||
isSaving?: boolean;
|
||||
saveError?: any;
|
||||
cluster?: Cluster;
|
||||
onConfigChange?: (cluster: ClusterPayload, hasErrors: boolean) => void;
|
||||
confirmFormText: ReactNode;
|
||||
backFormText: ReactNode;
|
||||
}
|
||||
|
||||
export type FormFields = ClusterPayload & {
|
||||
cloudRemoteAddress?: string;
|
||||
cloudAdvancedOptionsEnabled: boolean;
|
||||
};
|
||||
|
||||
interface State {
|
||||
fields: FormFields;
|
||||
fieldsErrors: ClusterErrors;
|
||||
areErrorsVisible: boolean;
|
||||
isRequestVisible: boolean;
|
||||
}
|
||||
|
||||
export class RemoteClusterForm extends Component<Props, State> {
|
||||
static defaultProps = {
|
||||
fields: merge({}, defaultClusterValues),
|
||||
};
|
||||
|
||||
static contextType = AppContext;
|
||||
private readonly generateId: (idSuffix?: string) => string;
|
||||
|
||||
declare context: Context;
|
||||
|
||||
constructor(props: Props, context: Context) {
|
||||
super(props, context);
|
||||
|
||||
const { cluster } = props;
|
||||
const { isCloudEnabled } = context;
|
||||
|
||||
// Connection mode should default to "proxy" in cloud
|
||||
const defaultMode = isCloudEnabled ? PROXY_MODE : SNIFF_MODE;
|
||||
const fieldsState: FormFields = merge(
|
||||
{},
|
||||
{
|
||||
...defaultClusterValues,
|
||||
mode: defaultMode,
|
||||
cloudRemoteAddress: cluster?.proxyAddress || '',
|
||||
cloudAdvancedOptionsEnabled: isCloudAdvancedOptionsEnabled(cluster),
|
||||
},
|
||||
cluster
|
||||
);
|
||||
|
||||
this.generateId = htmlIdGenerator();
|
||||
this.state = {
|
||||
fields: fieldsState,
|
||||
fieldsErrors: validateCluster(fieldsState, isCloudEnabled),
|
||||
areErrorsVisible: false,
|
||||
isRequestVisible: false,
|
||||
};
|
||||
}
|
||||
|
||||
toggleRequest = () => {
|
||||
this.setState(({ isRequestVisible }) => ({
|
||||
isRequestVisible: !isRequestVisible,
|
||||
}));
|
||||
};
|
||||
|
||||
onFieldsChange = (changedFields: Partial<FormFields>) => {
|
||||
const { isCloudEnabled } = this.context;
|
||||
|
||||
// when cloud remote address changes, fill proxy address and server name
|
||||
const { cloudRemoteAddress, cloudAdvancedOptionsEnabled } = changedFields;
|
||||
if (cloudRemoteAddress) {
|
||||
const { proxyAddress, serverName } =
|
||||
convertCloudRemoteAddressToProxyConnection(cloudRemoteAddress);
|
||||
// Only change the server name if the advanced options are not currently open
|
||||
if (this.state.fields.cloudAdvancedOptionsEnabled) {
|
||||
changedFields = {
|
||||
...changedFields,
|
||||
proxyAddress,
|
||||
};
|
||||
} else {
|
||||
changedFields = {
|
||||
...changedFields,
|
||||
proxyAddress,
|
||||
serverName,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// If we switch off the advanced options, revert the server name to
|
||||
// the host name from the proxy address
|
||||
if (cloudAdvancedOptionsEnabled === false) {
|
||||
changedFields = {
|
||||
...changedFields,
|
||||
serverName: this.state.fields.proxyAddress?.split(':')[0],
|
||||
proxySocketConnections: defaultClusterValues.proxySocketConnections,
|
||||
};
|
||||
}
|
||||
|
||||
this.setState(({ fields: prevFields }) => {
|
||||
const newFields = {
|
||||
...prevFields,
|
||||
...changedFields,
|
||||
};
|
||||
return {
|
||||
fields: newFields,
|
||||
fieldsErrors: validateCluster(newFields, isCloudEnabled),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
getCluster(): ClusterPayload {
|
||||
export const RemoteClusterForm: React.FC<Props> = ({
|
||||
confirmFormAction,
|
||||
onBack,
|
||||
isSaving,
|
||||
saveError,
|
||||
cluster,
|
||||
onConfigChange,
|
||||
confirmFormText,
|
||||
backFormText,
|
||||
}) => {
|
||||
const context = useContext(AppContext);
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { isCloudEnabled } = context;
|
||||
const defaultMode = isCloudEnabled ? PROXY_MODE : SNIFF_MODE;
|
||||
const initialFieldsState: FormFields = merge(
|
||||
{},
|
||||
{
|
||||
...defaultClusterValues,
|
||||
mode: defaultMode,
|
||||
cloudRemoteAddress: cluster?.proxyAddress || '',
|
||||
cloudAdvancedOptionsEnabled: isCloudAdvancedOptionsEnabled(cluster),
|
||||
},
|
||||
cluster
|
||||
);
|
||||
const [fields, setFields] = useState<FormFields>(initialFieldsState);
|
||||
const [fieldsErrors, setFieldsErrors] = useState<ClusterErrors>(
|
||||
validateCluster(initialFieldsState, isCloudEnabled)
|
||||
);
|
||||
const [areErrorsVisible, setAreErrorsVisible] = useState(false);
|
||||
const [formHasBeenSubmited, setFormHasBeenSubmited] = useState(false);
|
||||
const generateId = htmlIdGenerator();
|
||||
const getCluster = useCallback((): ClusterPayload => {
|
||||
const {
|
||||
fields: {
|
||||
name,
|
||||
mode,
|
||||
seeds,
|
||||
nodeConnections,
|
||||
proxyAddress,
|
||||
proxySocketConnections,
|
||||
serverName,
|
||||
skipUnavailable,
|
||||
},
|
||||
} = this.state;
|
||||
const { cluster } = this.props;
|
||||
name,
|
||||
mode,
|
||||
seeds,
|
||||
nodeConnections,
|
||||
proxyAddress,
|
||||
proxySocketConnections,
|
||||
serverName,
|
||||
skipUnavailable,
|
||||
} = fields;
|
||||
|
||||
let modeSettings;
|
||||
|
||||
if (mode === PROXY_MODE) {
|
||||
modeSettings = {
|
||||
proxyAddress,
|
||||
proxySocketConnections,
|
||||
serverName,
|
||||
};
|
||||
} else {
|
||||
modeSettings = {
|
||||
seeds,
|
||||
nodeConnections,
|
||||
};
|
||||
}
|
||||
const modeSettings =
|
||||
mode === PROXY_MODE
|
||||
? {
|
||||
proxyAddress,
|
||||
proxySocketConnections,
|
||||
serverName,
|
||||
}
|
||||
: {
|
||||
seeds,
|
||||
nodeConnections,
|
||||
};
|
||||
|
||||
return {
|
||||
name,
|
||||
|
@ -206,44 +127,88 @@ export class RemoteClusterForm extends Component<Props, State> {
|
|||
hasDeprecatedProxySetting: cluster?.hasDeprecatedProxySetting,
|
||||
...modeSettings,
|
||||
};
|
||||
}
|
||||
}, [fields, cluster]);
|
||||
|
||||
save = () => {
|
||||
const { save } = this.props;
|
||||
|
||||
if (this.hasErrors()) {
|
||||
this.setState({
|
||||
areErrorsVisible: true,
|
||||
});
|
||||
const handleNext = () => {
|
||||
if (hasErrors()) {
|
||||
setAreErrorsVisible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const cluster = this.getCluster();
|
||||
save(cluster);
|
||||
setFormHasBeenSubmited(true);
|
||||
confirmFormAction(getCluster());
|
||||
};
|
||||
|
||||
onSkipUnavailableChange = (e: EuiSwitchEvent) => {
|
||||
const skipUnavailable = e.target.checked;
|
||||
this.onFieldsChange({ skipUnavailable });
|
||||
};
|
||||
const onFieldsChange = useCallback(
|
||||
(changedFields: Partial<FormFields>) => {
|
||||
// when cloud remote address changes, fill proxy address and server name
|
||||
const { cloudRemoteAddress, cloudAdvancedOptionsEnabled } = changedFields;
|
||||
if (cloudRemoteAddress) {
|
||||
const { proxyAddress, serverName } =
|
||||
convertCloudRemoteAddressToProxyConnection(cloudRemoteAddress);
|
||||
// Only change the server name if the advanced options are not currently open
|
||||
if (fields.cloudAdvancedOptionsEnabled) {
|
||||
changedFields = {
|
||||
...changedFields,
|
||||
proxyAddress,
|
||||
};
|
||||
} else {
|
||||
changedFields = {
|
||||
...changedFields,
|
||||
proxyAddress,
|
||||
serverName,
|
||||
};
|
||||
}
|
||||
}
|
||||
// If we switch off the advanced options, revert the server name to
|
||||
// the host name from the proxy address
|
||||
if (cloudAdvancedOptionsEnabled === false) {
|
||||
changedFields = {
|
||||
...changedFields,
|
||||
serverName: fields.proxyAddress?.split(':')[0],
|
||||
proxySocketConnections: defaultClusterValues.proxySocketConnections,
|
||||
};
|
||||
}
|
||||
const newFields = {
|
||||
...fields,
|
||||
...changedFields,
|
||||
};
|
||||
setFields(newFields);
|
||||
setFieldsErrors(validateCluster(newFields, isCloudEnabled));
|
||||
},
|
||||
[fields, isCloudEnabled]
|
||||
);
|
||||
|
||||
resetToDefault = (fieldName: keyof ClusterPayload) => {
|
||||
this.onFieldsChange({
|
||||
[fieldName]: defaultClusterValues[fieldName],
|
||||
});
|
||||
};
|
||||
|
||||
hasErrors = () => {
|
||||
const { fieldsErrors } = this.state;
|
||||
const hasErrors = useCallback(() => {
|
||||
const errorValues = Object.values(fieldsErrors);
|
||||
return errorValues.some((error) => error != null);
|
||||
};
|
||||
}, [fieldsErrors]);
|
||||
|
||||
renderSkipUnavailable() {
|
||||
const {
|
||||
fields: { skipUnavailable },
|
||||
} = this.state;
|
||||
useEffect(() => {
|
||||
if (onConfigChange && formHasBeenSubmited) {
|
||||
const errors = hasErrors();
|
||||
setAreErrorsVisible(errors);
|
||||
onConfigChange(getCluster(), errors);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fields]);
|
||||
|
||||
const onSkipUnavailableChange = useCallback(
|
||||
(e: EuiSwitchEvent) => {
|
||||
const skipUnavailable = e.target.checked;
|
||||
onFieldsChange({ skipUnavailable });
|
||||
},
|
||||
[onFieldsChange]
|
||||
);
|
||||
const resetToDefault = useCallback(
|
||||
(fieldName: keyof ClusterPayload) => {
|
||||
onFieldsChange({
|
||||
[fieldName]: defaultClusterValues[fieldName],
|
||||
});
|
||||
},
|
||||
[onFieldsChange]
|
||||
);
|
||||
|
||||
const renderSkipUnavailable = () => {
|
||||
return (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
|
@ -257,44 +222,36 @@ export class RemoteClusterForm extends Component<Props, State> {
|
|||
</EuiTitle>
|
||||
}
|
||||
description={
|
||||
<Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription"
|
||||
defaultMessage="If any of the remote clusters are unavailable, the query request fails. To avoid this and continue to send requests to other clusters, enable {optionName}. {learnMoreLink}"
|
||||
values={{
|
||||
optionName: (
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription.optionNameLabel"
|
||||
defaultMessage="Skip if unavailable"
|
||||
/>
|
||||
</strong>
|
||||
),
|
||||
learnMoreLink: (
|
||||
<EuiLink href={skippingDisconnectedClustersUrl} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription.learnMoreLinkLabel"
|
||||
defaultMessage="Learn more."
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</Fragment>
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription"
|
||||
defaultMessage="If any of the remote clusters are unavailable, the query request fails. To avoid this and continue to send requests to other clusters, enable Skip if unavailable. {learnMoreLink}"
|
||||
values={{
|
||||
learnMoreLink: (
|
||||
<EuiLink href={skippingDisconnectedClustersUrl} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription.learnMoreLinkLabel"
|
||||
defaultMessage="Learn more."
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow
|
||||
data-test-subj="remoteClusterFormSkipUnavailableFormRow"
|
||||
className="remoteClusterSkipIfUnavailableSwitch"
|
||||
css={css`
|
||||
padding-top: ${euiTheme.size.s};
|
||||
`}
|
||||
fullWidth
|
||||
helpText={
|
||||
skipUnavailable !== defaultClusterValues.skipUnavailable ? (
|
||||
fields.skipUnavailable !== defaultClusterValues.skipUnavailable ? (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
this.resetToDefault('skipUnavailable');
|
||||
resetToDefault('skipUnavailable');
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -312,143 +269,25 @@ export class RemoteClusterForm extends Component<Props, State> {
|
|||
defaultMessage: 'Skip if unavailable',
|
||||
}
|
||||
)}
|
||||
checked={!!skipUnavailable}
|
||||
onChange={this.onSkipUnavailableChange}
|
||||
checked={!!fields.skipUnavailable}
|
||||
onChange={onSkipUnavailableChange}
|
||||
data-test-subj="remoteClusterFormSkipUnavailableFormToggle"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderActions() {
|
||||
const { isSaving, cancel, cluster: isEditMode } = this.props;
|
||||
const { areErrorsVisible, isRequestVisible } = this.state;
|
||||
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="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.nextButtonLabel"
|
||||
defaultMessage="{isEditMode, select, true{Save} other{Next}}"
|
||||
values={{
|
||||
isEditMode: Boolean(isEditMode),
|
||||
}}
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
renderSavingFeedback() {
|
||||
if (this.props.isSaving) {
|
||||
return (
|
||||
<EuiOverlayMask>
|
||||
<EuiLoadingLogo logo="logoKibana" size="xl" />
|
||||
</EuiOverlayMask>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderSaveErrorFeedback() {
|
||||
const { saveError } = this.props;
|
||||
|
||||
if (saveError) {
|
||||
const { message, cause } = saveError;
|
||||
|
||||
let errorBody;
|
||||
|
||||
if (cause && Array.isArray(cause)) {
|
||||
if (cause.length === 1) {
|
||||
errorBody = <p>{cause[0]}</p>;
|
||||
} else {
|
||||
errorBody = (
|
||||
<ul>
|
||||
{cause.map((causeValue) => (
|
||||
<li key={causeValue}>{causeValue}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiCallOut title={message} color="danger" iconType="error">
|
||||
{errorBody}
|
||||
</EuiCallOut>
|
||||
|
||||
<EuiSpacer />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderErrors = () => {
|
||||
const renderErrors = () => {
|
||||
const {
|
||||
areErrorsVisible,
|
||||
fieldsErrors: { name: errorClusterName, seeds: errorsSeeds, proxyAddress: errorProxyAddress },
|
||||
} = this.state;
|
||||
|
||||
const hasErrors = this.hasErrors();
|
||||
|
||||
if (!areErrorsVisible || !hasErrors) {
|
||||
name: errorClusterName,
|
||||
seeds: errorsSeeds,
|
||||
proxyAddress: errorProxyAddress,
|
||||
} = fieldsErrors;
|
||||
if (!areErrorsVisible || !hasErrors()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const errorExplanations = [];
|
||||
|
||||
if (errorClusterName) {
|
||||
errorExplanations.push({
|
||||
key: 'nameExplanation',
|
||||
|
@ -458,7 +297,6 @@ export class RemoteClusterForm extends Component<Props, State> {
|
|||
error: errorClusterName,
|
||||
});
|
||||
}
|
||||
|
||||
if (errorsSeeds) {
|
||||
errorExplanations.push({
|
||||
key: 'seedsExplanation',
|
||||
|
@ -468,7 +306,6 @@ export class RemoteClusterForm extends Component<Props, State> {
|
|||
error: errorsSeeds,
|
||||
});
|
||||
}
|
||||
|
||||
if (errorProxyAddress) {
|
||||
errorExplanations.push({
|
||||
key: 'proxyAddressExplanation',
|
||||
|
@ -478,10 +315,9 @@ export class RemoteClusterForm extends Component<Props, State> {
|
|||
error: errorProxyAddress,
|
||||
});
|
||||
}
|
||||
|
||||
const messagesToBeRendered = errorExplanations.length && (
|
||||
<EuiScreenReaderOnly>
|
||||
<dl id={this.generateId(ERROR_LIST_ID)} aria-labelledby={this.generateId(ERROR_TITLE_ID)}>
|
||||
<dl id={generateId(ERROR_LIST_ID)} aria-labelledby={generateId(ERROR_TITLE_ID)}>
|
||||
{errorExplanations.map(({ key, field, error }) => (
|
||||
<div key={key}>
|
||||
<dt>{field}</dt>
|
||||
|
@ -491,12 +327,11 @@ export class RemoteClusterForm extends Component<Props, State> {
|
|||
</dl>
|
||||
</EuiScreenReaderOnly>
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={
|
||||
<span id={this.generateId(ERROR_TITLE_ID)}>
|
||||
<span id={generateId(ERROR_TITLE_ID)}>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.errorTitle"
|
||||
defaultMessage="Some fields require your attention."
|
||||
|
@ -508,92 +343,102 @@ export class RemoteClusterForm extends Component<Props, State> {
|
|||
/>
|
||||
<EuiDelayRender>{messagesToBeRendered}</EuiDelayRender>
|
||||
<EuiSpacer size="m" data-test-subj="remoteClusterFormGlobalError" />
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isRequestVisible, areErrorsVisible, fields, fieldsErrors } = this.state;
|
||||
const { name: errorClusterName } = fieldsErrors;
|
||||
const { cluster } = this.props;
|
||||
const isNew = !cluster;
|
||||
return (
|
||||
<Fragment>
|
||||
{this.renderSaveErrorFeedback()}
|
||||
{this.renderErrors()}
|
||||
|
||||
<EuiForm data-test-subj="remoteClusterForm">
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<EuiTitle size="s">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionNameTitle"
|
||||
defaultMessage="Name"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
}
|
||||
description={
|
||||
const isNew = !cluster;
|
||||
return (
|
||||
<>
|
||||
{saveError && <SaveError saveError={saveError} />}
|
||||
{renderErrors()}
|
||||
<EuiForm data-test-subj="remoteClusterForm">
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<EuiTitle size="s">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionNameTitle"
|
||||
defaultMessage="Remote cluster name"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
}
|
||||
description={
|
||||
isCloudEnabled ? (
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cloud.sectionNameDescription"
|
||||
defaultMessage="A unique identifier for the remote cluster. Must match the {remoteClusterName} in this deployment’s Cloud -> Security settings."
|
||||
values={{
|
||||
remoteClusterName: (
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cloud.sectionNameDescription.remoteClusterName"
|
||||
defaultMessage="remote cluster name"
|
||||
/>
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionNameDescription"
|
||||
defaultMessage="A unique name for the cluster."
|
||||
id="xpack.remoteClusters.remoteClusterForm.stateful.sectionNameDescription"
|
||||
defaultMessage="A unique identifier for the remote cluster."
|
||||
/>
|
||||
)
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow
|
||||
data-test-subj="remoteClusterFormNameFormRow"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.fieldNameLabel"
|
||||
defaultMessage="Remote cluster name"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.fieldNameLabelHelpText"
|
||||
defaultMessage="Must contain only letters, numbers, underscores, and dashes."
|
||||
/>
|
||||
}
|
||||
error={fieldsErrors.name}
|
||||
isInvalid={Boolean(areErrorsVisible && fieldsErrors.name)}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow
|
||||
data-test-subj="remoteClusterFormNameFormRow"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.fieldNameLabel"
|
||||
defaultMessage="Name"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.fieldNameLabelHelpText"
|
||||
defaultMessage="Must contain only letters, numbers, underscores, and dashes."
|
||||
/>
|
||||
}
|
||||
error={errorClusterName}
|
||||
isInvalid={Boolean(areErrorsVisible && errorClusterName)}
|
||||
<EuiFieldText
|
||||
isInvalid={Boolean(areErrorsVisible && fieldsErrors.name)}
|
||||
value={fields.name}
|
||||
onChange={(e) => onFieldsChange({ name: e.target.value })}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFieldText
|
||||
isInvalid={Boolean(areErrorsVisible && errorClusterName)}
|
||||
value={fields.name}
|
||||
onChange={(e) => this.onFieldsChange({ name: e.target.value })}
|
||||
fullWidth
|
||||
disabled={!isNew}
|
||||
data-test-subj="remoteClusterFormNameInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
|
||||
<ConnectionMode
|
||||
fields={fields}
|
||||
fieldsErrors={fieldsErrors}
|
||||
onFieldsChange={this.onFieldsChange}
|
||||
areErrorsVisible={areErrorsVisible}
|
||||
/>
|
||||
|
||||
{this.renderSkipUnavailable()}
|
||||
</EuiForm>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
{this.renderActions()}
|
||||
|
||||
{this.renderSavingFeedback()}
|
||||
|
||||
{isRequestVisible ? (
|
||||
<RequestFlyout
|
||||
cluster={this.getCluster()}
|
||||
close={() => this.setState({ isRequestVisible: false })}
|
||||
/>
|
||||
) : null}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
disabled={!isNew}
|
||||
data-test-subj="remoteClusterFormNameInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
<ConnectionMode
|
||||
fields={fields}
|
||||
fieldsErrors={fieldsErrors}
|
||||
onFieldsChange={onFieldsChange}
|
||||
areErrorsVisible={areErrorsVisible}
|
||||
/>
|
||||
{renderSkipUnavailable()}
|
||||
</EuiForm>
|
||||
<EuiSpacer size="xl" />
|
||||
<ActionButtons
|
||||
showRequest={true}
|
||||
disabled={areErrorsVisible && hasErrors()}
|
||||
isSaving={isSaving}
|
||||
handleNext={handleNext}
|
||||
onBack={onBack}
|
||||
confirmFormText={confirmFormText}
|
||||
backFormText={backFormText}
|
||||
cluster={getCluster()}
|
||||
nextButtonTestSubj={'remoteClusterFormNextButton'}
|
||||
backButtonTestSubj={'remoteClusterFormBackButton'}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SECURITY_MODEL } from '../../../../../../../common/constants';
|
||||
import {
|
||||
isCloudAdvancedOptionsEnabled,
|
||||
validateCloudRemoteAddress,
|
||||
|
@ -35,7 +36,7 @@ describe('Cloud remote address', () => {
|
|||
const actual = isCloudAdvancedOptionsEnabled({
|
||||
name: 'test',
|
||||
proxyAddress: '',
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
});
|
||||
expect(actual).toBe(false);
|
||||
});
|
||||
|
@ -45,7 +46,7 @@ describe('Cloud remote address', () => {
|
|||
name: 'test',
|
||||
proxyAddress: 'some-proxy:9400',
|
||||
serverName: 'some-proxy',
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
});
|
||||
expect(actual).toBe(false);
|
||||
});
|
||||
|
@ -54,7 +55,7 @@ describe('Cloud remote address', () => {
|
|||
name: 'test',
|
||||
proxyAddress: 'some-proxy:9400',
|
||||
serverName: 'some-server-name',
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
});
|
||||
expect(actual).toBe(true);
|
||||
});
|
||||
|
@ -64,7 +65,7 @@ describe('Cloud remote address', () => {
|
|||
proxyAddress: 'some-proxy:9400',
|
||||
serverName: 'some-proxy-name',
|
||||
proxySocketConnections: 19,
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
});
|
||||
expect(actual).toBe(true);
|
||||
});
|
||||
|
|
|
@ -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 { RemoteClusterReview } from './remote_cluster_review';
|
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
* 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, { useContext } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiSpacer, EuiText, EuiLink, EuiSteps, EuiTitle, EuiStepsProps } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { RequestError } from '../../../../../types';
|
||||
import { SECURITY_MODEL } from '../../../../../../common/constants';
|
||||
import {
|
||||
apiKeys,
|
||||
cloudCreateApiKey,
|
||||
cloudSetupTrustUrl,
|
||||
onPremPrerequisitesApiKey,
|
||||
onPremSecurityApiKey,
|
||||
onPremPrerequisitesCert,
|
||||
onPremSecurityCert,
|
||||
} from '../../../../services/documentation';
|
||||
import { ClusterPayload } from '../../../../../../common/lib';
|
||||
import { AppContext } from '../../../../app_context';
|
||||
import { ActionButtons, SaveError } from '../components';
|
||||
|
||||
interface Props {
|
||||
onBack?: () => void;
|
||||
onSubmit: () => void;
|
||||
isSaving?: boolean;
|
||||
saveError?: RequestError;
|
||||
cluster: ClusterPayload;
|
||||
securityModel: string;
|
||||
}
|
||||
|
||||
export const RemoteClusterReview = ({
|
||||
onBack,
|
||||
onSubmit,
|
||||
isSaving,
|
||||
saveError,
|
||||
cluster,
|
||||
securityModel,
|
||||
}: Props) => {
|
||||
const context = useContext(AppContext);
|
||||
const { isCloudEnabled, cloudDeploymentUrl } = context;
|
||||
|
||||
const onPremSteps: EuiStepsProps['steps'] = [
|
||||
{
|
||||
title: i18n.translate('xpack.remoteClusters.remoteClusterForm.onPrem.step1.title', {
|
||||
defaultMessage: 'Confirm both clusters are compatible',
|
||||
}),
|
||||
status: 'incomplete',
|
||||
children: (
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.onPrem.step1.paragraph"
|
||||
defaultMessage="Ensure that both clusters meet the {requirmentsLink} needed to enable the connection."
|
||||
values={{
|
||||
requirmentsLink: (
|
||||
<EuiLink
|
||||
href={
|
||||
securityModel === SECURITY_MODEL.API
|
||||
? onPremPrerequisitesApiKey
|
||||
: onPremPrerequisitesCert
|
||||
}
|
||||
external={true}
|
||||
target="_blank"
|
||||
data-test-subj="remoteClusterReviewOnPremStep1"
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.remoteClusters.remoteClusterForm.onPrem.step1.requirements',
|
||||
{
|
||||
defaultMessage: 'requirements',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.remoteClusters.remoteClusterForm.onPrem.step2.title', {
|
||||
defaultMessage: 'Confirm trust is established',
|
||||
}),
|
||||
status: 'incomplete',
|
||||
children: (
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.onPrem.step2.paragraph"
|
||||
defaultMessage="Follow the {addRemoteClusterGuideLink} to establish trust between local and remote clusters."
|
||||
values={{
|
||||
addRemoteClusterGuideLink: (
|
||||
<EuiLink
|
||||
href={
|
||||
securityModel === SECURITY_MODEL.API ? onPremSecurityApiKey : onPremSecurityCert
|
||||
}
|
||||
external={true}
|
||||
target="_blank"
|
||||
data-test-subj="remoteClusterReviewOnPremStep2"
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.remoteClusters.remoteClusterForm.onPrem.step1.addClustersGuide',
|
||||
{
|
||||
defaultMessage: 'Add remote clusters guide',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const cloudSteps: EuiStepsProps['steps'] = [
|
||||
{
|
||||
title: i18n.translate('xpack.remoteClusters.remoteClusterForm.cloud.api.step1.title', {
|
||||
defaultMessage: 'Create a cross-cluster API key on the remote deployment',
|
||||
}),
|
||||
status: 'incomplete',
|
||||
'data-test-subj': 'cloudApiKeySteps',
|
||||
children: (
|
||||
<>
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cloud.api.step1.paragraph1"
|
||||
defaultMessage="On the remote cluster or deployment, use the {apiLink} or {kibanaLink} to create a cross-cluster API key. Configure it with access to the indices you want to use for cross-cluster search or cross-cluster replication."
|
||||
values={{
|
||||
apiLink: (
|
||||
<EuiLink href={cloudCreateApiKey} external={true} target="_blank">
|
||||
{i18n.translate(
|
||||
'xpack.remoteClusters.remoteClusterForm.cloud.api.step1.paragraph1.api',
|
||||
{
|
||||
defaultMessage: 'Elasticsearch API',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
kibanaLink: (
|
||||
<EuiLink href={apiKeys} external={true} target="_blank">
|
||||
{i18n.translate(
|
||||
'xpack.remoteClusters.remoteClusterForm.cloud.api.step1.paragraph1.kibana',
|
||||
{
|
||||
defaultMessage: 'Kibana',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cloud.api.step1.paragraph2"
|
||||
defaultMessage="Copy the encoded key (the “encoded” value from the response) to a safe location. You will need it in the next step."
|
||||
/>
|
||||
</EuiText>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.remoteClusters.remoteClusterForm.cloud.api.step2.title', {
|
||||
defaultMessage: 'Configure your local deployment',
|
||||
}),
|
||||
status: 'incomplete',
|
||||
children: (
|
||||
<>
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cloud.api.step2.intro"
|
||||
defaultMessage="Add the key that you created to your local deployment's keystore:"
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiText size="s">
|
||||
<ol>
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cloud.api.step2.list1"
|
||||
defaultMessage="Go to the {managementLink} page for your deployment."
|
||||
values={{
|
||||
managementLink: (
|
||||
<EuiLink href={cloudDeploymentUrl} target="_blank">
|
||||
{i18n.translate(
|
||||
'xpack.remoteClusters.remoteClusterForm.cloud.api.step2.list1.management',
|
||||
{
|
||||
defaultMessage: 'management',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cloud.api.step2.list2"
|
||||
defaultMessage="From the navigation menu, click Security."
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cloud.api.step2.list3"
|
||||
defaultMessage="In the Remote connections section, add your API key."
|
||||
/>
|
||||
</li>
|
||||
</ol>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cloud.api.step2.end"
|
||||
defaultMessage="Check {documentationLink} for more detailed instructions."
|
||||
values={{
|
||||
documentationLink: (
|
||||
<EuiLink href={cloudSetupTrustUrl} external={true} target="_blank">
|
||||
{i18n.translate(
|
||||
'xpack.remoteClusters.remoteClusterForm.cloud.api.step2.end.documentation',
|
||||
{
|
||||
defaultMessage: 'documentation',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const getOnPremInfo = () => {
|
||||
return (
|
||||
<>
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.onPrem.disclaimerInfo"
|
||||
defaultMessage="Check that the following requirements are completed to ensure that both clusters can communicate:"
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiSteps
|
||||
data-test-subj="remoteClusterReviewOnPremSteps"
|
||||
titleSize="s"
|
||||
steps={onPremSteps}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getCloudInfo = () => {
|
||||
return securityModel === SECURITY_MODEL.API ? (
|
||||
<>
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cloud.disclaimerInfo"
|
||||
defaultMessage="Make sure you complete the steps below before proceeding with saving this configuration."
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiSteps titleSize="s" steps={cloudSteps} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cloud.cert.title"
|
||||
defaultMessage="Confirm trust is established."
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiText size="s" data-test-subj="cloudCertDocumentation">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cloud.cert.paragraph"
|
||||
defaultMessage="Before you proceed, ensure that trust is correctly configured between both clusters. If all requirements are not met, the remote cluster won't connect. {detailsLink}"
|
||||
values={{
|
||||
detailsLink: (
|
||||
<EuiLink href={cloudSetupTrustUrl} external={true} target="_blank">
|
||||
{i18n.translate(
|
||||
'xpack.remoteClusters.remoteClusterForm.cloud.cert.paragraph.documentationLink',
|
||||
{
|
||||
defaultMessage: 'Read details.',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
data-test-subj="cloudCertDocumentation"
|
||||
/>
|
||||
</EuiText>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{saveError && <SaveError saveError={saveError} />}
|
||||
{isCloudEnabled ? getCloudInfo() : getOnPremInfo()}
|
||||
<EuiSpacer size="xl" />
|
||||
<ActionButtons
|
||||
showRequest={true}
|
||||
isSaving={isSaving}
|
||||
handleNext={onSubmit}
|
||||
onBack={onBack}
|
||||
confirmFormText={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.addClusterButtonLabel"
|
||||
defaultMessage="Add remote cluster"
|
||||
/>
|
||||
}
|
||||
backFormText={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.backButtonLabel"
|
||||
defaultMessage="Back"
|
||||
/>
|
||||
}
|
||||
cluster={cluster}
|
||||
nextButtonTestSubj={'remoteClusterReviewtNextButton'}
|
||||
backButtonTestSubj={'remoteClusterReviewtBackButton'}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,107 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { 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',
|
||||
})}
|
||||
labelProps={{
|
||||
'data-test-subj': 'remoteClusterTrustCheckboxLabel',
|
||||
}}
|
||||
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>
|
||||
);
|
||||
};
|
|
@ -5,22 +5,20 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState, useContext } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { SECURITY_MODEL } from '../../../../../../common/constants';
|
||||
import {
|
||||
EuiSpacer,
|
||||
EuiCard,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
} from '@elastic/eui';
|
||||
} from '../../../../../shared_imports';
|
||||
|
||||
import * as docs from '../../../../services/documentation';
|
||||
import { AppContext } from '../../../../app_context';
|
||||
import { ConfirmTrustSetupModal } from './confirm_modal';
|
||||
import { ActionButtons } from '../components';
|
||||
|
||||
const MIN_ALLOWED_VERSION_API_KEYS_METHOD = '8.10';
|
||||
const CARD_MAX_WIDTH = 400;
|
||||
|
@ -48,21 +46,23 @@ const i18nTexts = {
|
|||
),
|
||||
};
|
||||
|
||||
const docLinks = {
|
||||
cert: docs.onPremSetupTrustWithCertUrl,
|
||||
apiKey: docs.onPremSetupTrustWithApiKeyUrl,
|
||||
cloud: docs.cloudSetupTrustUrl,
|
||||
};
|
||||
|
||||
interface Props {
|
||||
onBack: () => void;
|
||||
onSubmit: () => void;
|
||||
isSaving: boolean;
|
||||
next: (model: string) => void;
|
||||
onSecurityChange: (model: string) => void;
|
||||
currentSecurityModel: string;
|
||||
}
|
||||
|
||||
export const RemoteClusterSetupTrust = ({ onBack, onSubmit, isSaving }: Props) => {
|
||||
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
|
||||
const { canUseAPIKeyTrustModel, isCloudEnabled } = useContext(AppContext);
|
||||
export const RemoteClusterSetupTrust = ({
|
||||
next,
|
||||
currentSecurityModel,
|
||||
onSecurityChange,
|
||||
}: Props) => {
|
||||
const { canUseAPIKeyTrustModel } = useContext(AppContext);
|
||||
const [securityModel, setSecurityModel] = useState<string>(currentSecurityModel);
|
||||
|
||||
useEffect(() => {
|
||||
onSecurityChange(securityModel);
|
||||
}, [onSecurityChange, securityModel]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -70,123 +70,72 @@ export const RemoteClusterSetupTrust = ({ onBack, onSubmit, isSaving }: Props) =
|
|||
<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 />,
|
||||
}}
|
||||
defaultMessage="Set up an authentication mechanism to connect to the remote cluster."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="xxl" />
|
||||
|
||||
<EuiFlexGroup wrap justifyContent="center">
|
||||
<EuiFlexGroup gutterSize="l" wrap justifyContent="center">
|
||||
{canUseAPIKeyTrustModel && (
|
||||
<EuiFlexItem style={{ maxWidth: CARD_MAX_WIDTH }}>
|
||||
<EuiCard
|
||||
title={i18nTexts.apiKeyTitle}
|
||||
paddingSize="l"
|
||||
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>
|
||||
data-test-subj="setupTrustApiMode"
|
||||
title={i18nTexts.apiKeyTitle}
|
||||
description={i18nTexts.apiKeyDescription}
|
||||
footer={
|
||||
<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>
|
||||
}
|
||||
selectable={{
|
||||
onClick: () => {
|
||||
setSecurityModel(SECURITY_MODEL.API);
|
||||
},
|
||||
isSelected: securityModel === SECURITY_MODEL.API,
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
data-test-subj="setupTrustCertMode"
|
||||
title={i18nTexts.certTitle}
|
||||
description={i18nTexts.certDescription}
|
||||
selectable={{
|
||||
onClick: () => {
|
||||
setSecurityModel(SECURITY_MODEL.CERTIFICATE);
|
||||
},
|
||||
isSelected: securityModel === SECURITY_MODEL.CERTIFICATE,
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
<ActionButtons
|
||||
showRequest={false}
|
||||
disabled={!securityModel}
|
||||
handleNext={() => {
|
||||
next(securityModel);
|
||||
}}
|
||||
confirmFormText={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.nextButtonLabel"
|
||||
defaultMessage="Next"
|
||||
/>
|
||||
}
|
||||
nextButtonTestSubj={'remoteClusterTrustNextButton'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,14 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiPageHeader, EuiButtonEmpty, EuiSpacer } from '@elastic/eui';
|
||||
import { remoteClustersUrl } from '../../../services/documentation';
|
||||
|
||||
import { EuiPageHeader, EuiButtonEmpty, EuiSpacer } from '@elastic/eui';
|
||||
interface Props {
|
||||
title: ReactNode;
|
||||
description?: ReactNode;
|
||||
}
|
||||
|
||||
export const RemoteClusterPageTitle = ({ title, description }) => (
|
||||
export const RemoteClusterPageTitle: React.FC<Props> = ({ title, description }) => (
|
||||
<>
|
||||
<EuiPageHeader
|
||||
bottomBorder
|
||||
|
@ -38,8 +41,3 @@ export const RemoteClusterPageTitle = ({ title, description }) => (
|
|||
<EuiSpacer size="l" />
|
||||
</>
|
||||
);
|
||||
|
||||
RemoteClusterPageTitle.propTypes = {
|
||||
title: PropTypes.node.isRequired,
|
||||
description: PropTypes.node,
|
||||
};
|
|
@ -6,22 +6,23 @@
|
|||
*/
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { ClusterPayload } from '../../../../common/lib';
|
||||
import { RemoteClusterAdd as RemoteClusterAddView } from './remote_cluster_add';
|
||||
|
||||
import { isAddingCluster, getAddClusterError } from '../../store/selectors';
|
||||
|
||||
import { addCluster, clearAddClusterErrors } from '../../store/actions';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = (state: any) => {
|
||||
return {
|
||||
isAddingCluster: isAddingCluster(state),
|
||||
addClusterError: getAddClusterError(state),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
const mapDispatchToProps = (dispatch: (action: any) => void) => {
|
||||
return {
|
||||
addCluster: (cluster) => {
|
||||
addCluster: (cluster: ClusterPayload) => {
|
||||
dispatch(addCluster(cluster));
|
||||
},
|
||||
clearAddClusterErrors: () => {
|
||||
|
@ -30,4 +31,14 @@ const mapDispatchToProps = (dispatch) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const RemoteClusterAdd = connect(mapStateToProps, mapDispatchToProps)(RemoteClusterAddView);
|
||||
interface Props {
|
||||
addCluster: (cluster: ClusterPayload) => void;
|
||||
isAddingCluster: boolean;
|
||||
addClusterError?: { message: string };
|
||||
clearAddClusterErrors: () => void;
|
||||
}
|
||||
|
||||
export const RemoteClusterAdd: React.FC<Props> = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(RemoteClusterAddView);
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { 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 } from '../components';
|
||||
import { RemoteClusterWizard } from './wizard_form';
|
||||
|
||||
export class RemoteClusterAdd extends PureComponent {
|
||||
static propTypes = {
|
||||
addCluster: PropTypes.func,
|
||||
isAddingCluster: PropTypes.bool,
|
||||
addClusterError: PropTypes.object,
|
||||
clearAddClusterErrors: PropTypes.func,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
setBreadcrumbs('add');
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Clean up after ourselves.
|
||||
this.props.clearAddClusterErrors();
|
||||
}
|
||||
|
||||
save = (clusterConfig) => {
|
||||
this.props.addCluster(clusterConfig);
|
||||
};
|
||||
|
||||
redirectToList = () => {
|
||||
const {
|
||||
history,
|
||||
route: {
|
||||
location: { search },
|
||||
},
|
||||
} = getRouter();
|
||||
const { redirect: redirectUrl } = extractQueryParams(search);
|
||||
|
||||
if (redirectUrl) {
|
||||
const decodedRedirect = decodeURIComponent(redirectUrl);
|
||||
redirect(decodedRedirect);
|
||||
} else {
|
||||
history.push('/list');
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isAddingCluster, addClusterError } = this.props;
|
||||
|
||||
return (
|
||||
<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."
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<RemoteClusterWizard
|
||||
saveRemoteClusterConfig={this.save}
|
||||
onCancel={this.redirectToList}
|
||||
isSaving={isAddingCluster}
|
||||
addClusterError={addClusterError}
|
||||
/>
|
||||
</EuiPageSection>
|
||||
</EuiPageBody>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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, { useEffect } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiPageSection, EuiPageBody } from '@elastic/eui';
|
||||
|
||||
import { ClusterPayload } from '../../../../common/lib';
|
||||
import { extractQueryParams } from '../../../shared_imports';
|
||||
import { getRouter, redirect } from '../../services';
|
||||
import { setBreadcrumbs } from '../../services/breadcrumb';
|
||||
import { RemoteClusterPageTitle } from '../components';
|
||||
import { RemoteClusterWizard } from './wizard_form';
|
||||
|
||||
interface Props {
|
||||
addCluster: (cluster: ClusterPayload) => void;
|
||||
isAddingCluster: boolean;
|
||||
addClusterError?: { message: string };
|
||||
clearAddClusterErrors: () => void;
|
||||
}
|
||||
|
||||
export const RemoteClusterAdd: React.FC<Props> = ({
|
||||
addCluster,
|
||||
isAddingCluster,
|
||||
addClusterError,
|
||||
clearAddClusterErrors,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
setBreadcrumbs('add');
|
||||
return () => {
|
||||
// Clean up after ourselves.
|
||||
clearAddClusterErrors();
|
||||
};
|
||||
}, [clearAddClusterErrors]);
|
||||
|
||||
const redirectToList = () => {
|
||||
const {
|
||||
route: {
|
||||
location: { search },
|
||||
},
|
||||
history,
|
||||
} = getRouter();
|
||||
|
||||
const { redirect: redirectUrl } = extractQueryParams(search);
|
||||
|
||||
if (redirectUrl && typeof redirectUrl === 'string') {
|
||||
const decodedRedirect = decodeURIComponent(redirectUrl);
|
||||
redirect(decodedRedirect);
|
||||
} else {
|
||||
history.push('/list');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<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="Add a remote cluster that connects to seed nodes or to a single proxy address."
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<RemoteClusterWizard
|
||||
saveRemoteClusterConfig={addCluster}
|
||||
onCancel={redirectToList}
|
||||
isSaving={isAddingCluster}
|
||||
addClusterError={addClusterError}
|
||||
/>
|
||||
</EuiPageSection>
|
||||
</EuiPageBody>
|
||||
);
|
||||
};
|
|
@ -5,15 +5,20 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState, useMemo, useEffect } from 'react';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiStepsHorizontal, EuiStepStatus, EuiSpacer, EuiPageSection } from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { RemoteClusterSetupTrust, RemoteClusterForm } from '../components';
|
||||
import { ClusterPayload } from '../../../../common/lib/cluster_serialization';
|
||||
import { RemoteClusterReview } from '../components/remote_cluster_config_steps/remote_cluster_review';
|
||||
|
||||
const CONFIGURE_CONNECTION = 1;
|
||||
const SETUP_TRUST = 2;
|
||||
const SETUP_TRUST = 1;
|
||||
const CONFIGURE_CONNECTION = 2;
|
||||
const REVIEW = 3;
|
||||
|
||||
const FORM_MAX_WIDTH = 850;
|
||||
|
||||
interface Props {
|
||||
saveRemoteClusterConfig: (config: ClusterPayload) => void;
|
||||
|
@ -29,53 +34,73 @@ export const RemoteClusterWizard = ({
|
|||
addClusterError,
|
||||
}: Props) => {
|
||||
const [formState, setFormState] = useState<ClusterPayload>();
|
||||
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 [formHasErrors, setFormHasErrors] = useState(false);
|
||||
const [currentStep, setCurrentStep] = useState(SETUP_TRUST);
|
||||
const [securityModel, setSecurityModel] = useState('');
|
||||
|
||||
const stepDefinitions = useMemo(
|
||||
() => [
|
||||
{
|
||||
step: SETUP_TRUST,
|
||||
title: i18n.translate('xpack.remoteClusters.clusterWizard.selectConnectionTypeLabel', {
|
||||
defaultMessage: 'Select connection type',
|
||||
}),
|
||||
status: (currentStep === SETUP_TRUST ? 'current' : 'complete') as EuiStepStatus,
|
||||
onClick: () => setCurrentStep(SETUP_TRUST),
|
||||
},
|
||||
{
|
||||
step: CONFIGURE_CONNECTION,
|
||||
title: i18n.translate('xpack.remoteClusters.clusterWizard.addConnectionInfoLabel', {
|
||||
defaultMessage: 'Add connection information',
|
||||
}),
|
||||
disabled: !securityModel,
|
||||
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',
|
||||
step: REVIEW,
|
||||
title: i18n.translate('xpack.remoteClusters.clusterWizard.confirmSetup', {
|
||||
defaultMessage: 'Confirm setup',
|
||||
}),
|
||||
status: (currentStep === SETUP_TRUST ? 'current' : 'incomplete') as EuiStepStatus,
|
||||
disabled: !formState,
|
||||
onClick: () => setCurrentStep(SETUP_TRUST),
|
||||
disabled: !formState || formHasErrors,
|
||||
status: (currentStep === REVIEW ? 'current' : 'incomplete') as EuiStepStatus,
|
||||
onClick: () => setCurrentStep(REVIEW),
|
||||
},
|
||||
],
|
||||
[currentStep, formState, setCurrentStep]
|
||||
[currentStep, formHasErrors, formState, securityModel]
|
||||
);
|
||||
|
||||
const completeTrustStep = (model: string) => {
|
||||
setSecurityModel(model);
|
||||
setCurrentStep(CONFIGURE_CONNECTION);
|
||||
};
|
||||
const onSecurityUpdate = (model: string) => {
|
||||
if (securityModel !== '') {
|
||||
setSecurityModel(model);
|
||||
}
|
||||
};
|
||||
|
||||
// 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: ClusterPayload) => {
|
||||
setFormState(clusterConfig);
|
||||
setCurrentStep(SETUP_TRUST);
|
||||
setCurrentStep(REVIEW);
|
||||
};
|
||||
|
||||
const completeTrustStep = () => {
|
||||
const onConfigUpdate = (clusterConfig: ClusterPayload, hasErrors: boolean) => {
|
||||
if (formState !== undefined) {
|
||||
setFormState(clusterConfig);
|
||||
setFormHasErrors(hasErrors);
|
||||
}
|
||||
};
|
||||
|
||||
const completeReviewStep = () => {
|
||||
saveRemoteClusterConfig(formState as ClusterPayload);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPageSection restrictWidth>
|
||||
<EuiPageSection restrictWidth={FORM_MAX_WIDTH}>
|
||||
<EuiStepsHorizontal steps={stepDefinitions} />
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
|
@ -83,21 +108,45 @@ export const RemoteClusterWizard = ({
|
|||
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}
|
||||
next={completeTrustStep}
|
||||
currentSecurityModel={securityModel}
|
||||
onSecurityChange={onSecurityUpdate}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div style={{ display: currentStep === CONFIGURE_CONNECTION ? 'block' : 'none' }}>
|
||||
<RemoteClusterForm
|
||||
confirmFormAction={completeConfigStep}
|
||||
onBack={() => setCurrentStep(SETUP_TRUST)}
|
||||
onConfigChange={onConfigUpdate}
|
||||
confirmFormText={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.nextButtonLabel"
|
||||
defaultMessage="Next"
|
||||
/>
|
||||
}
|
||||
backFormText={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.backButtonLabel"
|
||||
defaultMessage="Back"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: currentStep === REVIEW ? 'block' : 'none' }}>
|
||||
{formState && (
|
||||
<RemoteClusterReview
|
||||
onBack={() => setCurrentStep(CONFIGURE_CONNECTION)}
|
||||
isSaving={isSaving}
|
||||
saveError={addClusterError}
|
||||
onSubmit={completeReviewStep}
|
||||
cluster={formState}
|
||||
securityModel={securityModel}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</EuiPageSection>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { Cluster, ClusterPayload } from '../../../../common/lib';
|
||||
import { RemoteClusterEdit as RemoteClusterEditView } from './remote_cluster_edit';
|
||||
|
||||
import {
|
||||
|
@ -23,7 +24,7 @@ import {
|
|||
openDetailPanel,
|
||||
} from '../../store/actions';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = (state: any) => {
|
||||
return {
|
||||
isLoading: isLoading(state),
|
||||
cluster: getEditedCluster(state),
|
||||
|
@ -32,27 +33,39 @@ const mapStateToProps = (state) => {
|
|||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
const mapDispatchToProps = (dispatch: (action: any) => void) => {
|
||||
return {
|
||||
startEditingCluster: (clusterName) => {
|
||||
startEditingCluster: (clusterName: string) => {
|
||||
dispatch(startEditingCluster({ clusterName }));
|
||||
},
|
||||
stopEditingCluster: () => {
|
||||
dispatch(stopEditingCluster());
|
||||
},
|
||||
editCluster: (cluster) => {
|
||||
editCluster: (cluster: ClusterPayload) => {
|
||||
dispatch(editCluster(cluster));
|
||||
},
|
||||
clearEditClusterErrors: () => {
|
||||
dispatch(clearEditClusterErrors());
|
||||
},
|
||||
openDetailPanel: (clusterName) => {
|
||||
openDetailPanel: (clusterName: string) => {
|
||||
dispatch(openDetailPanel({ name: clusterName }));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const RemoteClusterEdit = connect(
|
||||
interface Props {
|
||||
isLoading: boolean;
|
||||
cluster: Cluster;
|
||||
startEditingCluster: (clusterName: string) => void;
|
||||
stopEditingCluster: () => void;
|
||||
editCluster: (cluster: ClusterPayload) => void;
|
||||
isEditingCluster: boolean;
|
||||
getEditClusterError?: object;
|
||||
clearEditClusterErrors: () => void;
|
||||
openDetailPanel: (clusterName: string) => void;
|
||||
}
|
||||
|
||||
export const RemoteClusterEdit: React.FC<Props> = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(RemoteClusterEditView);
|
|
@ -1,226 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCallOut,
|
||||
EuiPageTemplate,
|
||||
EuiPageSection,
|
||||
EuiPageBody,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public';
|
||||
import { extractQueryParams, SectionLoading } from '../../../shared_imports';
|
||||
import { getRouter, redirect } from '../../services';
|
||||
import { setBreadcrumbs } from '../../services/breadcrumb';
|
||||
import { RemoteClusterPageTitle, RemoteClusterForm } from '../components';
|
||||
|
||||
export class RemoteClusterEdit extends Component {
|
||||
static propTypes = {
|
||||
isLoading: PropTypes.bool,
|
||||
cluster: PropTypes.object,
|
||||
startEditingCluster: PropTypes.func,
|
||||
stopEditingCluster: PropTypes.func,
|
||||
editCluster: PropTypes.func,
|
||||
isEditingCluster: PropTypes.bool,
|
||||
getEditClusterError: PropTypes.object,
|
||||
clearEditClusterErrors: PropTypes.func,
|
||||
openDetailPanel: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const {
|
||||
match: {
|
||||
params: { name },
|
||||
},
|
||||
} = props;
|
||||
|
||||
setBreadcrumbs('edit', `?cluster=${name}`);
|
||||
|
||||
this.state = {
|
||||
clusterName: name,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { startEditingCluster } = this.props;
|
||||
const { clusterName } = this.state;
|
||||
startEditingCluster(clusterName);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Clean up after ourselves.
|
||||
this.props.clearEditClusterErrors();
|
||||
this.props.stopEditingCluster();
|
||||
}
|
||||
|
||||
save = (clusterConfig) => {
|
||||
this.props.editCluster(clusterConfig);
|
||||
};
|
||||
|
||||
cancel = () => {
|
||||
const { openDetailPanel } = this.props;
|
||||
const { clusterName } = this.state;
|
||||
const {
|
||||
history,
|
||||
route: {
|
||||
location: { search },
|
||||
},
|
||||
} = getRouter();
|
||||
const { redirect: redirectUrl } = extractQueryParams(search);
|
||||
|
||||
if (redirectUrl) {
|
||||
const decodedRedirect = decodeURIComponent(redirectUrl);
|
||||
redirect(decodedRedirect);
|
||||
} else {
|
||||
history.push('/list');
|
||||
openDetailPanel(clusterName);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { clusterName } = this.state;
|
||||
|
||||
const { isLoading, cluster, isEditingCluster, getEditClusterError } = this.props;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SectionLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.loadingLabel"
|
||||
defaultMessage="Loading remote cluster…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
);
|
||||
}
|
||||
|
||||
if (!cluster) {
|
||||
return (
|
||||
<EuiPageTemplate.EmptyPrompt
|
||||
iconType="warning"
|
||||
color="danger"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.loadingErrorTitle"
|
||||
defaultMessage="Error loading remote cluster"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.loadingErrorMessage"
|
||||
defaultMessage="The remote cluster ''{name}'' does not exist."
|
||||
values={{ name: clusterName }}
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
actions={
|
||||
<EuiButton
|
||||
{...reactRouterNavigate(this.props.history, '/list')}
|
||||
color="danger"
|
||||
iconType="arrowLeft"
|
||||
flush="left"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.viewRemoteClustersButtonLabel"
|
||||
defaultMessage="View remote clusters"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const { isConfiguredByNode, hasDeprecatedProxySetting } = cluster;
|
||||
|
||||
if (isConfiguredByNode) {
|
||||
return (
|
||||
<EuiPageTemplate.EmptyPrompt
|
||||
iconType="iInCircle"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.configuredByNodeWarningTitle"
|
||||
defaultMessage="Defined in configuration"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.configuredByNodeWarningBody"
|
||||
defaultMessage="You can't edit or delete this remote cluster because it's defined in a node's
|
||||
elasticsearch.yml configuration file."
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
actions={
|
||||
<EuiButton color="primary" iconType="arrowLeft" flush="left" onClick={this.cancel}>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.backToRemoteClustersButtonLabel"
|
||||
defaultMessage="Back to remote clusters"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPageBody restrictWidth={true} data-test-subj="remote-clusters-edit">
|
||||
<EuiPageSection paddingSize="none">
|
||||
<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"
|
||||
>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* 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, { useEffect } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCallOut,
|
||||
EuiPageTemplate,
|
||||
EuiPageSection,
|
||||
EuiPageBody,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public';
|
||||
import { useRouteMatch } from 'react-router-dom';
|
||||
import { RequestError } from '../../../types';
|
||||
import { Cluster, ClusterPayload } from '../../../../common/lib';
|
||||
import { extractQueryParams, SectionLoading } from '../../../shared_imports';
|
||||
import { getRouter, redirect } from '../../services';
|
||||
import { setBreadcrumbs } from '../../services/breadcrumb';
|
||||
import { RemoteClusterPageTitle, RemoteClusterForm } from '../components';
|
||||
|
||||
const FORM_MAX_WIDTH = 850;
|
||||
|
||||
interface Props {
|
||||
isLoading: boolean;
|
||||
cluster: Cluster;
|
||||
startEditingCluster: (clusterName: string) => void;
|
||||
stopEditingCluster: () => void;
|
||||
editCluster: (cluster: ClusterPayload) => void;
|
||||
isEditingCluster: boolean;
|
||||
getEditClusterError?: RequestError;
|
||||
clearEditClusterErrors: () => void;
|
||||
openDetailPanel: (clusterName: string) => void;
|
||||
}
|
||||
|
||||
export const RemoteClusterEdit: React.FC<Props> = ({
|
||||
isLoading,
|
||||
cluster,
|
||||
startEditingCluster,
|
||||
stopEditingCluster,
|
||||
editCluster,
|
||||
isEditingCluster,
|
||||
getEditClusterError,
|
||||
clearEditClusterErrors,
|
||||
openDetailPanel,
|
||||
}) => {
|
||||
const match = useRouteMatch<{ name: string }>();
|
||||
const { name: clusterName } = match.params;
|
||||
const {
|
||||
history,
|
||||
route: {
|
||||
location: { search },
|
||||
},
|
||||
} = getRouter();
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs('edit', `?cluster=${clusterName}`);
|
||||
startEditingCluster(clusterName);
|
||||
|
||||
return () => {
|
||||
clearEditClusterErrors();
|
||||
stopEditingCluster();
|
||||
};
|
||||
}, [clusterName, startEditingCluster, clearEditClusterErrors, stopEditingCluster]);
|
||||
|
||||
const cancel = () => {
|
||||
const { redirect: redirectUrl } = extractQueryParams(search);
|
||||
|
||||
if (redirectUrl && typeof redirectUrl === 'string') {
|
||||
const decodedRedirect = decodeURIComponent(redirectUrl);
|
||||
redirect(decodedRedirect);
|
||||
} else {
|
||||
history.push('/list');
|
||||
openDetailPanel(clusterName);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SectionLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.loadingLabel"
|
||||
defaultMessage="Loading remote cluster…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
);
|
||||
}
|
||||
|
||||
if (!cluster) {
|
||||
return (
|
||||
<EuiPageTemplate.EmptyPrompt
|
||||
iconType="warning"
|
||||
color="danger"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.loadingErrorTitle"
|
||||
defaultMessage="Error loading remote cluster"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.loadingErrorMessage"
|
||||
defaultMessage="The remote cluster ''{name}'' does not exist."
|
||||
values={{ name: clusterName }}
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
actions={
|
||||
<EuiButton {...reactRouterNavigate(history, '/list')} color="danger" iconType="arrowLeft">
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.edit.viewRemoteClustersButtonLabel"
|
||||
defaultMessage="View remote clusters"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const { hasDeprecatedProxySetting } = cluster;
|
||||
|
||||
return (
|
||||
<EuiPageBody restrictWidth={true} data-test-subj="remote-clusters-edit">
|
||||
<EuiPageSection restrictWidth={FORM_MAX_WIDTH}>
|
||||
<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"
|
||||
>
|
||||
<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}
|
||||
confirmFormAction={editCluster}
|
||||
onBack={cancel}
|
||||
confirmFormText={
|
||||
<FormattedMessage id="xpack.remoteClusters.edit.save" defaultMessage="Save" />
|
||||
}
|
||||
backFormText={
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiPageSection>
|
||||
</EuiPageBody>
|
||||
);
|
||||
};
|
|
@ -9,7 +9,7 @@ import React from 'react';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiText, EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
|
||||
|
||||
import { getSecurityModel } from '../../../../../../common/constants';
|
||||
import { SECURITY_MODEL, getSecurityModel } from '../../../../../../common/constants';
|
||||
import { Cluster } from '../../../../../../common/lib/cluster_serialization';
|
||||
|
||||
export function SecurityModel({ securityModel }: { securityModel: Cluster['securityModel'] }) {
|
||||
|
@ -21,7 +21,7 @@ export function SecurityModel({ securityModel }: { securityModel: Cluster['secur
|
|||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
{securityModel !== 'api_key' && (
|
||||
{securityModel !== SECURITY_MODEL.API && (
|
||||
<EuiFlexItem grow={false} data-test-subj="authenticationTypeWarning">
|
||||
<EuiIconTip
|
||||
type="iInCircle"
|
||||
|
|
|
@ -12,9 +12,13 @@ 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 let apiKeys: string;
|
||||
export let cloudCreateApiKey: string;
|
||||
export let onPremPrerequisitesApiKey: string;
|
||||
export let onPremSecurityApiKey: string;
|
||||
export let onPremPrerequisitesCert: string;
|
||||
export let onPremSecurityCert: string;
|
||||
|
||||
export function init({ links }: DocLinksStart): void {
|
||||
skippingDisconnectedClustersUrl = links.ccs.skippingDisconnectedClusters;
|
||||
|
@ -22,7 +26,11 @@ 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;
|
||||
apiKeys = links.management.apiKeys;
|
||||
cloudCreateApiKey = links.elasticsearch.remoteClustersCreateCloudClusterApiKey;
|
||||
onPremPrerequisitesApiKey = links.elasticsearch.remoteClustersOnPremPrerequisitesApiKey;
|
||||
onPremSecurityApiKey = links.elasticsearch.remoteClustersOnPremSecurityApiKey;
|
||||
onPremPrerequisitesCert = links.elasticsearch.remoteClustersOnPremPrerequisitesCert;
|
||||
onPremSecurityCert = links.elasticsearch.remoteClustersOnPremSecurityCert;
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ export class RemoteClustersUIPlugin
|
|||
|
||||
const isCloudEnabled: boolean = Boolean(cloud?.isCloudEnabled);
|
||||
const cloudBaseUrl: string = cloud?.baseUrl ?? '';
|
||||
const cloudDeploymentUrl: string = cloud?.deploymentUrl ?? '';
|
||||
|
||||
const { renderApp } = await import('./application');
|
||||
const unmountAppCallback = await renderApp(
|
||||
|
@ -75,6 +76,7 @@ export class RemoteClustersUIPlugin
|
|||
{
|
||||
isCloudEnabled,
|
||||
cloudBaseUrl,
|
||||
cloudDeploymentUrl,
|
||||
executionContext,
|
||||
canUseAPIKeyTrustModel: this.canUseApiKeyTrustModel,
|
||||
},
|
||||
|
|
|
@ -7,6 +7,18 @@
|
|||
|
||||
export { extractQueryParams, indices, SectionLoading } from '@kbn/es-ui-shared-plugin/public';
|
||||
|
||||
export { useExecutionContext } from '@kbn/kibana-react-plugin/public';
|
||||
export { useExecutionContext, useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
export { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
|
||||
export { ViewApiRequestFlyout } from '@kbn/es-ui-shared-plugin/public';
|
||||
|
||||
export {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiCard,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
|
|
@ -27,6 +27,11 @@ export interface ClientConfigType {
|
|||
};
|
||||
}
|
||||
|
||||
export interface RequestError {
|
||||
message: string;
|
||||
cause?: string[];
|
||||
}
|
||||
|
||||
export type { RegisterManagementAppArgs };
|
||||
|
||||
export type { I18nStart };
|
||||
|
|
|
@ -14,7 +14,7 @@ import { httpServerMock, httpServiceMock, coreMock } from '@kbn/core/server/mock
|
|||
import { kibanaResponseFactory } from '@kbn/core/server';
|
||||
import { licensingMock } from '@kbn/licensing-plugin/server/mocks';
|
||||
|
||||
import { API_BASE_PATH } from '../../../common/constants';
|
||||
import { API_BASE_PATH, SECURITY_MODEL } from '../../../common/constants';
|
||||
|
||||
import { handleEsError } from '../../shared_imports';
|
||||
|
||||
|
@ -127,7 +127,7 @@ describe('GET remote clusters', () => {
|
|||
skipUnavailable: false,
|
||||
isConfiguredByNode: false,
|
||||
mode: 'sniff',
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { httpServerMock, httpServiceMock, coreMock } from '@kbn/core/server/mock
|
|||
|
||||
import { licensingMock } from '@kbn/licensing-plugin/server/mocks';
|
||||
|
||||
import { API_BASE_PATH } from '../../../common/constants';
|
||||
import { API_BASE_PATH, SECURITY_MODEL } from '../../../common/constants';
|
||||
|
||||
import { handleEsError } from '../../shared_imports';
|
||||
|
||||
|
@ -127,7 +127,7 @@ describe('UPDATE remote clusters', () => {
|
|||
seeds: ['127.0.0.1:9300'],
|
||||
skipUnavailable: true,
|
||||
mode: 'sniff',
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
});
|
||||
|
||||
expect(remoteInfoMockFn).toHaveBeenCalledWith();
|
||||
|
@ -207,7 +207,7 @@ describe('UPDATE remote clusters', () => {
|
|||
name: 'test',
|
||||
skipUnavailable: true,
|
||||
mode: 'proxy',
|
||||
securityModel: 'certificate',
|
||||
securityModel: SECURITY_MODEL.CERTIFICATE,
|
||||
});
|
||||
|
||||
expect(remoteInfoMockFn).toHaveBeenCalledWith();
|
||||
|
|
|
@ -35000,22 +35000,11 @@
|
|||
"xpack.remoteClusters.cloudDeploymentForm.remoteAddressInvalidError": "L'adresse distante n'est pas valide.",
|
||||
"xpack.remoteClusters.cloudDeploymentForm.remoteAddressRequiredError": "Une adresse distante est requise.",
|
||||
"xpack.remoteClusters.clusterWizard.addConnectionInfoLabel": "Ajouter des informations de connexion",
|
||||
"xpack.remoteClusters.clusterWizard.setupTrustLabel": "Établir la confiance",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.apiKeyNote": "Les deux clusters doivent disposer de la version {minAllowedVersion} ou supérieure.",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.backButtonLabel": "Retour",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.body": "Avez-vous mis en place un système de confiance pour vous connecter à votre cluster distant ?",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.docs": "Voir les instructions",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.doneButtonLabel": "Ajouter un cluster distant",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.modal.cancelButton": "Non, revenir en arrière",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.modal.checkbox": "Oui, j'ai mis en place un système de confiance",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.modal.createCluster": "Ajouter un cluster distant",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.modal.title": "Confirmer votre configuration",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.setupWithApiKeys.description": "Accès fin aux index distants. Il vous faut une clé d'API fournie par l'administrateur du cluster distant.",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.setupWithApiKeys.title": "Clés d'API",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.setupWithCert.description": "Accès complet au cluster distant. Il vous faut les certificats TLS du cluster distant.",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.setupWithCert.title": "Certificats",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.title": "Configurez un système d'authentification pour se connecter au cluster distant. Effectuez{br} cette étape en suivant nos instructions avant de continuer.",
|
||||
"xpack.remoteClusters.configuredByNodeWarningBody": "Vous ne pouvez pas modifier ni supprimer ce cluster distant, car il est défini dans le fichier de configuration elasticsearch.yml d'un nœud.",
|
||||
"xpack.remoteClusters.configuredByNodeWarningTitle": "Vous ne pouvez pas modifier ni supprimer ce cluster distant, car il est défini dans le fichier de configuration elasticsearch.yml d'un nœud.",
|
||||
"xpack.remoteClusters.connectedStatus.connectedAriaLabel": "Connecté",
|
||||
"xpack.remoteClusters.connectedStatus.notConnectedAriaLabel": "Non connecté",
|
||||
|
@ -35051,8 +35040,6 @@
|
|||
"xpack.remoteClusters.detailPanel.skipUnavailableNullValue": "Par défaut",
|
||||
"xpack.remoteClusters.detailPanel.skipUnavailableTrueValue": "Oui",
|
||||
"xpack.remoteClusters.detailPanel.statusTitle": "Statut",
|
||||
"xpack.remoteClusters.edit.backToRemoteClustersButtonLabel": "Retour aux clusters distants",
|
||||
"xpack.remoteClusters.edit.configuredByNodeWarningTitle": "Défini dans la configuration",
|
||||
"xpack.remoteClusters.edit.deprecatedSettingsMessage": "Ce cluster comprend des paramètres déclassés que nous avons essayé de résoudre. Vérifiez toutes les modifications avant d'enregistrer.",
|
||||
"xpack.remoteClusters.edit.deprecatedSettingsTitle": "Procéder avec prudence",
|
||||
"xpack.remoteClusters.edit.loadingErrorMessage": "Le cluster distant \"{name}\" n'existe pas.",
|
||||
|
@ -35104,18 +35091,14 @@
|
|||
"xpack.remoteClusters.remoteClusterForm.localSeedError.duplicateMessage": "Les nœuds initiaux en double ne sont pas autorisés.`",
|
||||
"xpack.remoteClusters.remoteClusterForm.localSeedError.invalidCharactersMessage": "Le nœud initial 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.localSeedError.invalidPortMessage": "Un port est requis.",
|
||||
"xpack.remoteClusters.remoteClusterForm.nextButtonLabel": "{isEditMode, select, true{Sauvegarder} other{Suivant}}",
|
||||
"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.sectionModeCloudDescription": "Configurez la manière de se connecter au cluster distant.",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeDescription": "Utilisez les nœuds initiaux par défaut, ou passez au mode proxy.",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeTitle": "Mode de connexion",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionNameDescription": "Nom unique pour le cluster.",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionNameTitle": "Nom",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSeedsHelpText.portLinkText": "port",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription": "Si l'un des clusters distants n'est pas disponible, la demande de requête échoue. Pour éviter ceci et continuer à envoyer des requêtes aux autres clusters, activez {optionName}. {learnMoreLink}",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription.learnMoreLinkLabel": "En savoir plus.",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription.optionNameLabel": "Ignorer si indisponible",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableLabel": "Ignorer si indisponible",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableResetLabel": "Réinitialiser aux valeurs par défaut",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableTitle": "Rendre le cluster distant facultatif",
|
||||
|
|
|
@ -34860,22 +34860,11 @@
|
|||
"xpack.remoteClusters.cloudDeploymentForm.remoteAddressInvalidError": "リモートアドレスが無効です。",
|
||||
"xpack.remoteClusters.cloudDeploymentForm.remoteAddressRequiredError": "リモートアドレスが必要です。",
|
||||
"xpack.remoteClusters.clusterWizard.addConnectionInfoLabel": "接続情報を追加",
|
||||
"xpack.remoteClusters.clusterWizard.setupTrustLabel": "信頼を確立",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.apiKeyNote": "両方のクラスターがバージョン{minAllowedVersion}以上である必要があります。",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.backButtonLabel": "戻る",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.body": "リモートクラスターに接続するための信頼を設定しましたか?",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.docs": "手順を表示",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.doneButtonLabel": "リモートクラスターを追加",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.modal.cancelButton": "いいえ。戻ります",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.modal.checkbox": "はい。信頼を設定しました",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.modal.createCluster": "リモートクラスターを追加",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.modal.title": "構成を確認",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.setupWithApiKeys.description": "リモートインデックスへのきめ細かいアクセス。リモートクラスター管理者が提供するAPIキーが必要です。",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.setupWithApiKeys.title": "APIキー",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.setupWithCert.description": "リモートクラスターへのフルアクセスリモートクラスターのTLS証明書が必要です。",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.setupWithCert.title": "証明書",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.title": "リモートクラスターに接続するための認証メカニズムを設定します。続行する前に、ドキュメントの手順を使用して、このステップを完了{br}してください。",
|
||||
"xpack.remoteClusters.configuredByNodeWarningBody": "このリモートクラスターはノードの elasticsearch.yml 構成ファイルで定義されているため、編集または削除できません。",
|
||||
"xpack.remoteClusters.configuredByNodeWarningTitle": "このリモートクラスターはノードの elasticsearch.yml 構成ファイルで定義されているため、編集または削除できません。",
|
||||
"xpack.remoteClusters.connectedStatus.connectedAriaLabel": "接続済み",
|
||||
"xpack.remoteClusters.connectedStatus.notConnectedAriaLabel": "未接続",
|
||||
|
@ -34911,8 +34900,6 @@
|
|||
"xpack.remoteClusters.detailPanel.skipUnavailableNullValue": "デフォルト",
|
||||
"xpack.remoteClusters.detailPanel.skipUnavailableTrueValue": "はい",
|
||||
"xpack.remoteClusters.detailPanel.statusTitle": "ステータス",
|
||||
"xpack.remoteClusters.edit.backToRemoteClustersButtonLabel": "リモートクラスターに戻る",
|
||||
"xpack.remoteClusters.edit.configuredByNodeWarningTitle": "構成で定義",
|
||||
"xpack.remoteClusters.edit.deprecatedSettingsMessage": "このリモートクラスターには解決を試みた非推奨設定があります。保存する前にすべての変更を検証してください。",
|
||||
"xpack.remoteClusters.edit.deprecatedSettingsTitle": "十分ご注意ください",
|
||||
"xpack.remoteClusters.edit.loadingErrorMessage": "リモートクラスター''{name}''が存在しません。",
|
||||
|
@ -34964,18 +34951,14 @@
|
|||
"xpack.remoteClusters.remoteClusterForm.localSeedError.duplicateMessage": "重複シードノードは使用できません。`",
|
||||
"xpack.remoteClusters.remoteClusterForm.localSeedError.invalidCharactersMessage": "シードノードはホストポートのフォーマットを使用する必要があります。例:127.0.0.1:9400、localhost:9400ホストには文字、数字、ハイフンのみが使用できます。",
|
||||
"xpack.remoteClusters.remoteClusterForm.localSeedError.invalidPortMessage": "ポートが必要です。",
|
||||
"xpack.remoteClusters.remoteClusterForm.nextButtonLabel": "{isEditMode, select, true{保存} other{次へ}}",
|
||||
"xpack.remoteClusters.remoteClusterForm.proxyError.invalidCharactersMessage": "アドレスはホスト:ポートの形式にする必要があります。例:127.0.0.1:9400、localhost:9400ホストには文字、数字、ハイフンのみが使用できます。",
|
||||
"xpack.remoteClusters.remoteClusterForm.proxyError.missingProxyMessage": "プロキシアドレスが必要です。",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeCloudDescription": "リモートクラスターに接続する方法を構成します。",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeDescription": "既定でシードノードを使用するか、プロキシモードに切り替えます。",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeTitle": "接続モード",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionNameDescription": "クラスターの固有の名前です。",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionNameTitle": "名前",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSeedsHelpText.portLinkText": "ポート",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription": "リモートクラスターのいずれかが利用不能な場合、クエリーリクエストは失敗します。これを回避して、他のクラスターにリクエストを送信し続けるには、{optionName}。{learnMoreLink}",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription.learnMoreLinkLabel": "詳細情報",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription.optionNameLabel": "利用不可の場合スキップ",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableLabel": "利用不可の場合スキップ",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableResetLabel": "デフォルトにリセット",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableTitle": "リモートクラスターをオプションにする",
|
||||
|
|
|
@ -34953,22 +34953,11 @@
|
|||
"xpack.remoteClusters.cloudDeploymentForm.remoteAddressInvalidError": "远程地址无效。",
|
||||
"xpack.remoteClusters.cloudDeploymentForm.remoteAddressRequiredError": "“远程地址”必填。",
|
||||
"xpack.remoteClusters.clusterWizard.addConnectionInfoLabel": "添加连接信息",
|
||||
"xpack.remoteClusters.clusterWizard.setupTrustLabel": "建立信任",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.apiKeyNote": "两个集群都必须为 {minAllowedVersion} 版本或更高版本。",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.backButtonLabel": "返回",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.body": "是否已建立信任以连接到远程集群?",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.docs": "查看说明",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.doneButtonLabel": "添加远程集群",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.modal.cancelButton": "否,返回",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.modal.checkbox": "是,我已建立信任",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.modal.createCluster": "添加远程集群",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.modal.title": "确认您的配置",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.setupWithApiKeys.description": "远程索引的细粒度访问权限。您需要由远程集群管理员提供的 API 密钥。",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.setupWithApiKeys.title": "API 密钥",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.setupWithCert.description": "远程集群的完全访问权限。您需要来自远程集群的 TLS 证书。",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.setupWithCert.title": "证书",
|
||||
"xpack.remoteClusters.clusterWizard.trustStep.title": "设置身份验证机制以连接到远程集群。按照文档中的说明完成{br}此步骤,然后继续。",
|
||||
"xpack.remoteClusters.configuredByNodeWarningBody": "您无法编辑或删除此远程集群,因为它是在节点的 elasticsearch.yml 配置文件中定义的。",
|
||||
"xpack.remoteClusters.configuredByNodeWarningTitle": "您无法编辑或删除此远程集群,因为它是在节点的 elasticsearch.yml 配置文件中定义的。",
|
||||
"xpack.remoteClusters.connectedStatus.connectedAriaLabel": "已连接",
|
||||
"xpack.remoteClusters.connectedStatus.notConnectedAriaLabel": "未连接",
|
||||
|
@ -35004,8 +34993,6 @@
|
|||
"xpack.remoteClusters.detailPanel.skipUnavailableNullValue": "默认",
|
||||
"xpack.remoteClusters.detailPanel.skipUnavailableTrueValue": "是",
|
||||
"xpack.remoteClusters.detailPanel.statusTitle": "状态",
|
||||
"xpack.remoteClusters.edit.backToRemoteClustersButtonLabel": "返回远程集群",
|
||||
"xpack.remoteClusters.edit.configuredByNodeWarningTitle": "已在配置中定义",
|
||||
"xpack.remoteClusters.edit.deprecatedSettingsMessage": "此远程集群具有我们已尝试解决的过时设置。保存之前确认所有更改。",
|
||||
"xpack.remoteClusters.edit.deprecatedSettingsTitle": "谨慎操作",
|
||||
"xpack.remoteClusters.edit.loadingErrorMessage": "远程集群“{name}”不存在。",
|
||||
|
@ -35057,18 +35044,14 @@
|
|||
"xpack.remoteClusters.remoteClusterForm.localSeedError.duplicateMessage": "不允许重复的种子节点。`",
|
||||
"xpack.remoteClusters.remoteClusterForm.localSeedError.invalidCharactersMessage": "种子节点必须使用 host:port 格式。例如:127.0.0.1:9400、localhost:9400。主机只能由字母、数字和短划线构成。",
|
||||
"xpack.remoteClusters.remoteClusterForm.localSeedError.invalidPortMessage": "端口必填。",
|
||||
"xpack.remoteClusters.remoteClusterForm.nextButtonLabel": "{isEditMode, select, true{保存} other{下一步}}",
|
||||
"xpack.remoteClusters.remoteClusterForm.proxyError.invalidCharactersMessage": "地址必须使用 host:port 格式。例如:127.0.0.1:9400、localhost:9400。主机只能由字母、数字和短划线构成。",
|
||||
"xpack.remoteClusters.remoteClusterForm.proxyError.missingProxyMessage": "必须指定代理地址。",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeCloudDescription": "配置如何连接到远程集群。",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeDescription": "默认使用种子节点或切换到代理模式。",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionModeTitle": "连接模式",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionNameDescription": "集群的唯一名称。",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionNameTitle": "名称",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSeedsHelpText.portLinkText": "端口",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription": "如果任何远程集群都不可用,查询请求将失败。要避免此问题并继续向其他集群发送请求,请启用 {optionName}。{learnMoreLink}",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription.learnMoreLinkLabel": "了解详情。",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableDescription.optionNameLabel": "如果不可用,则跳过",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableLabel": "如果不可用,则跳过",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableResetLabel": "重置为默认值",
|
||||
"xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableTitle": "使远程集群可选",
|
||||
|
|
|
@ -39,7 +39,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
describe('follower index form', () => {
|
||||
before(async () => {
|
||||
await PageObjects.common.navigateToApp('remoteClusters');
|
||||
await PageObjects.remoteClusters.createNewRemoteCluster(remoteName, 'localhost:9300');
|
||||
await PageObjects.remoteClusters.createNewRemoteCluster(
|
||||
remoteName,
|
||||
'localhost:9300',
|
||||
false
|
||||
);
|
||||
await es.indices.create({ index: testIndex });
|
||||
await es.indices.create({ index: testLeader });
|
||||
});
|
||||
|
|
|
@ -18,6 +18,11 @@ const deleteModalTitle = 'confirmModalTitleText';
|
|||
const detailsTitle = 'remoteClusterDetailsFlyoutTitle';
|
||||
const requestButton = 'remoteClustersRequestButton';
|
||||
const requestTitle = 'remoteClusterRequestFlyoutTitle';
|
||||
const selectApiKeyButton = 'setupTrustApiMode';
|
||||
const trustStepNextButton = 'remoteClusterTrustNextButton';
|
||||
const formStepNextButton = 'remoteClusterFormNextButton';
|
||||
const addRemoteClusterButton = 'remoteClusterReviewtNextButton';
|
||||
const closeFlyoutButton = 'euiFlyoutCloseButton';
|
||||
|
||||
interface Payload {
|
||||
persistent: {
|
||||
|
@ -80,7 +85,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
const retry = getService('retry');
|
||||
|
||||
describe('Remote Clusters Accessibility', () => {
|
||||
beforeEach(async () => {
|
||||
before(async () => {
|
||||
await PageObjects.common.navigateToApp('remoteClusters');
|
||||
});
|
||||
|
||||
|
@ -92,29 +97,34 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
it('renders add remote cluster form', async () => {
|
||||
await retry.waitFor('add remote cluster button to be rendered', async () => {
|
||||
it('renders add remote cluster form - trust step', async () => {
|
||||
await retry.waitFor('add remote cluster button to be rendered - trust step', async () => {
|
||||
return testSubjects.isDisplayed(createButton);
|
||||
});
|
||||
|
||||
await testSubjects.click(createButton);
|
||||
await retry.waitFor('add remote cluster form to be rendered', async () => {
|
||||
await retry.waitFor('add remote cluster form to be rendered - trust step', async () => {
|
||||
return (await testSubjects.getVisibleText(pageTitle)) === 'Add remote cluster';
|
||||
});
|
||||
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
it('renders add remote cluster form - form step', async () => {
|
||||
await retry.waitFor('select api key button to be rendered - form step', async () => {
|
||||
return testSubjects.isDisplayed(selectApiKeyButton);
|
||||
});
|
||||
|
||||
await testSubjects.click(selectApiKeyButton);
|
||||
await testSubjects.click(trustStepNextButton);
|
||||
await retry.waitFor('next button form step to be rendered', async () => {
|
||||
return testSubjects.isDisplayed(formStepNextButton);
|
||||
});
|
||||
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
it('renders request flyout', async () => {
|
||||
await retry.waitFor('add remote cluster button to be rendered', async () => {
|
||||
return testSubjects.isDisplayed(createButton);
|
||||
});
|
||||
|
||||
await testSubjects.click(createButton);
|
||||
await retry.waitFor('add remote cluster form to be rendered', async () => {
|
||||
return (await testSubjects.getVisibleText(pageTitle)) === 'Add remote cluster';
|
||||
});
|
||||
|
||||
await testSubjects.click(requestButton);
|
||||
await retry.waitFor('request flyout to be rendered', async () => {
|
||||
return (await testSubjects.getVisibleText(requestTitle)) === 'Request';
|
||||
|
@ -122,6 +132,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
it('renders add remote cluster form - review step', async () => {
|
||||
await testSubjects.click(closeFlyoutButton);
|
||||
await testSubjects.setValue('remoteClusterFormNameInput', 'testRemoteCluster');
|
||||
await testSubjects.setValue('remoteClusterFormSeedsInput', '1:1');
|
||||
await testSubjects.click(formStepNextButton);
|
||||
|
||||
await retry.waitFor('add remote cluster button to be rendered', async () => {
|
||||
return testSubjects.isDisplayed(addRemoteClusterButton);
|
||||
});
|
||||
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
const modes = ['sniff', 'proxy'];
|
||||
|
@ -133,6 +156,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
mode === 'sniff'
|
||||
? getPayloadClusterSniffMode(clusterName)
|
||||
: getPayloadClusterProxyMode(clusterName);
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.common.navigateToApp('remoteClusters');
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
await esClient.cluster.putSettings({ body });
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
|
|||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('feature controls', function () {
|
||||
this.tags('skipCloud');
|
||||
loadTestFile(require.resolve('./remote_clusters_security'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
|
||||
describe('Home page', function () {
|
||||
before(async () => {
|
||||
this.tags('skipCloud');
|
||||
await security.testUser.setRoles(['global_ccr_role']);
|
||||
await pageObjects.common.navigateToApp('remoteClusters');
|
||||
});
|
||||
|
|
|
@ -12,8 +12,8 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
// https://www.elastic.co/guide/en/kibana/7.9/working-remote-clusters.html
|
||||
export default ({ loadTestFile }: FtrProviderContext) => {
|
||||
describe('Remote Clusters app', function () {
|
||||
this.tags('skipCloud');
|
||||
loadTestFile(require.resolve('./feature_controls'));
|
||||
loadTestFile(require.resolve('./home_page'));
|
||||
loadTestFile(require.resolve('./remote_clusters'));
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const security = getService('security');
|
||||
const deployment = getService('deployment');
|
||||
const pageObjects = getPageObjects(['common', 'remoteClusters']);
|
||||
const testSubjects = getService('testSubjects');
|
||||
const es = getService('es');
|
||||
|
||||
const REMOTE_CLUSTER_NAME = 'testName';
|
||||
const HOST_PORT = 'test:9400';
|
||||
|
||||
describe('remote clusters', () => {
|
||||
let isCloud: boolean;
|
||||
before(async () => {
|
||||
isCloud = await deployment.isCloud();
|
||||
await security.testUser.setRoles(['global_ccr_role']);
|
||||
await pageObjects.common.navigateToApp('remoteClusters');
|
||||
await pageObjects.remoteClusters.createNewRemoteCluster(
|
||||
REMOTE_CLUSTER_NAME,
|
||||
HOST_PORT,
|
||||
isCloud
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await es.cluster.putSettings({
|
||||
persistent: {
|
||||
cluster: {
|
||||
remote: {
|
||||
[REMOTE_CLUSTER_NAME]: {
|
||||
mode: null,
|
||||
skip_unavailable: null,
|
||||
node_connections: null,
|
||||
seeds: null,
|
||||
server_name: null,
|
||||
proxy_socket_connections: null,
|
||||
proxy_address: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a remote cluster', async () => {
|
||||
expect(await testSubjects.exists('remoteClusterDetailsFlyoutTitle')).to.be(true);
|
||||
expect(await testSubjects.getVisibleText('remoteClusterDetailsFlyoutTitle')).to.be(
|
||||
REMOTE_CLUSTER_NAME
|
||||
);
|
||||
|
||||
const hostFieldId = isCloud ? 'remoteClusterDetailProxyAddress' : 'remoteClusterDetailSeeds';
|
||||
expect(await testSubjects.exists(hostFieldId)).to.be(true);
|
||||
expect(await testSubjects.getVisibleText(hostFieldId)).to.be(HOST_PORT);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -9,34 +9,46 @@ import { FtrProviderContext } from '../ftr_provider_context';
|
|||
|
||||
export function RemoteClustersPageProvider({ getService }: FtrProviderContext) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const comboBox = getService('comboBox');
|
||||
const retry = getService('retry');
|
||||
|
||||
enum ConnectionType {
|
||||
'cert' = 'setupTrustCertMode',
|
||||
'api' = 'setupTrustApiMode',
|
||||
}
|
||||
|
||||
return {
|
||||
async remoteClusterCreateButton() {
|
||||
return await testSubjects.find('remoteClusterEmptyPromptCreateButton');
|
||||
},
|
||||
async createNewRemoteCluster(
|
||||
name: string,
|
||||
seedNode: string,
|
||||
proxyMode?: boolean,
|
||||
nodeConnections?: number,
|
||||
skipIfUnavailable?: boolean
|
||||
host: string,
|
||||
isCloud: boolean,
|
||||
trustMode: 'cert' | 'api' = 'cert'
|
||||
) {
|
||||
await (await this.remoteClusterCreateButton()).click();
|
||||
await retry.waitFor('remote cluster form to be visible', async () => {
|
||||
return await testSubjects.isDisplayed('remoteClusterFormNameInput');
|
||||
await retry.waitFor('setup trust tab to be visible', async () => {
|
||||
return await testSubjects.isDisplayed('remoteClusterTrustNextButton');
|
||||
});
|
||||
await testSubjects.click(ConnectionType[trustMode]);
|
||||
await testSubjects.click('remoteClusterTrustNextButton');
|
||||
|
||||
await retry.waitFor('form tab to be visible', async () => {
|
||||
return await testSubjects.isDisplayed('remoteClusterFormNextButton');
|
||||
});
|
||||
|
||||
await testSubjects.setValue('remoteClusterFormNameInput', name);
|
||||
await comboBox.setCustom('comboBoxInput', seedNode);
|
||||
const hostFieldId = isCloud
|
||||
? 'remoteClusterFormRemoteAddressInput'
|
||||
: 'remoteClusterFormSeedsInput';
|
||||
await testSubjects.setValue(hostFieldId, host);
|
||||
await testSubjects.click('remoteClusterFormNextButton');
|
||||
|
||||
await retry.waitFor('review tab to be visible', async () => {
|
||||
return await testSubjects.isDisplayed('remoteClusterReviewtNextButton');
|
||||
});
|
||||
// Submit config form
|
||||
await testSubjects.click('remoteClusterFormSaveButton');
|
||||
|
||||
// Complete trust setup
|
||||
await testSubjects.click('setupTrustDoneButton');
|
||||
await testSubjects.setCheckbox('remoteClusterTrustCheckboxLabel', 'check');
|
||||
await testSubjects.click('remoteClusterTrustSubmitButton');
|
||||
await testSubjects.click('remoteClusterReviewtNextButton');
|
||||
},
|
||||
async getRemoteClustersList() {
|
||||
const table = await testSubjects.find('remoteClusterListTable');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue