[S&R] Support data streams (#68078)
* Sort endpoint responses into indices and datastreams The server endpoint for policies now returns data streams and filters out backing indices from the indices array it returned previously * Refactor indices switch and field out of the step settings file * Fix indices field form behaviour * WiP on UI. Added the second table per mockup for add and edit. * add support for creating a policy that backs up data streams end to end * wip on restore flow - added data streams to server response * add logic for detecting whether an index is part of a data stream * fix public jest tests * fix server side jest tests * pivot to different solution in UI while we do not have data streams nicely separated * added data stream to snapshot summary details * move the data streams badge file closer to where it used * add data stream badge when restoring snapshots too * update restore copy * fix pattern specification in indices and data streams field * first iteration of complete policy UX * First iteration that is ready for review Given the contraints on working with data streams and indices in policies at the moment the simplest implementation is to just include data streams with indices and have the user select them there for now. The way snapshotting behaviour is currently implemented relies entirely on what is specified inside of "indices", this is also where data streams must be placed. This unfortunately means that capture patterns defined in indices will capture entire data streams too. * delete unused import * fix type issue in tests * added logic for rendering out previous selection as custom pattern * refactor indices fields to make component smaller * added CIT for data streams badge * Data streams > indices * updates to relevant pieces of copy * more copy updates * fix types and remove unused import * removed backing indices from restore view * Added data stream restore warning message * restore CITs * first round of copy feedback * refactor help text to provide clearer feedback, for both restore and policy forms * Restore updates - added spacer between title and data streams callout - added copy to the restore settings tab to indicate that settings also apply to backing indices * further copy refinements * second round of copy feedback * fix i18n * added comment to mock * line spacing fixes and created issue for tracking backing index discovery in snaphots * refactor collapsible list logic and tests * refactor editing managed policy check * refactor copy to be clearer about pluralisation of data streams * refactor file structure in components for data stream badge * added tests for indices and data streams field helper * refactored types and fixed i18n id per guidelines Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
9c76f19186
commit
0066c4b5b0
60 changed files with 1716 additions and 737 deletions
|
@ -3,12 +3,13 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import './mocks';
|
||||
import { setup as homeSetup } from './home.helpers';
|
||||
import { setup as repositoryAddSetup } from './repository_add.helpers';
|
||||
import { setup as repositoryEditSetup } from './repository_edit.helpers';
|
||||
import { setup as policyAddSetup } from './policy_add.helpers';
|
||||
import { setup as policyEditSetup } from './policy_edit.helpers';
|
||||
import { setup as restoreSnapshotSetup } from './restore_snapshot.helpers';
|
||||
|
||||
export { nextTick, getRandomString, findTestSubject, TestBed } from '../../../../../test_utils';
|
||||
|
||||
|
@ -20,4 +21,5 @@ export const pageHelpers = {
|
|||
repositoryEdit: { setup: repositoryEditSetup },
|
||||
policyAdd: { setup: policyAddSetup },
|
||||
policyEdit: { setup: policyEditSetup },
|
||||
restoreSnapshot: { setup: restoreSnapshotSetup },
|
||||
};
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
/*
|
||||
* Mocking AutoSizer of the react-virtualized because it does not render children in JS DOM.
|
||||
* This seems related to not being able to properly discover height and width.
|
||||
*/
|
||||
jest.mock('react-virtualized', () => {
|
||||
const original = jest.requireActual('react-virtualized');
|
||||
|
||||
return {
|
||||
...original,
|
||||
AutoSizer: ({ children }: { children: any }) => (
|
||||
<div>{children({ height: 500, width: 500 })}</div>
|
||||
),
|
||||
};
|
||||
});
|
|
@ -41,6 +41,8 @@ export type PolicyFormTestSubjects =
|
|||
| 'allIndicesToggle'
|
||||
| 'backButton'
|
||||
| 'deselectIndicesLink'
|
||||
| 'allDataStreamsToggle'
|
||||
| 'deselectDataStreamLink'
|
||||
| 'expireAfterValueInput'
|
||||
| 'expireAfterUnitSelect'
|
||||
| 'ignoreUnavailableIndicesToggle'
|
||||
|
@ -53,4 +55,5 @@ export type PolicyFormTestSubjects =
|
|||
| 'selectIndicesLink'
|
||||
| 'showAdvancedCronLink'
|
||||
| 'snapshotNameInput'
|
||||
| 'dataStreamBadge'
|
||||
| 'submitButton';
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @kbn/eslint/no-restricted-paths */
|
||||
import { registerTestBed, TestBed, TestBedConfig } from '../../../../../test_utils';
|
||||
import { RestoreSnapshot } from '../../../public/application/sections/restore_snapshot';
|
||||
import { WithAppDependencies } from './setup_environment';
|
||||
|
||||
const testBedConfig: TestBedConfig = {
|
||||
memoryRouter: {
|
||||
initialEntries: ['/add_policy'],
|
||||
componentRoutePath: '/add_policy',
|
||||
},
|
||||
doMountAsync: true,
|
||||
};
|
||||
|
||||
const initTestBed = registerTestBed<RestoreSnapshotFormTestSubject>(
|
||||
WithAppDependencies(RestoreSnapshot),
|
||||
testBedConfig
|
||||
);
|
||||
|
||||
const setupActions = (testBed: TestBed<RestoreSnapshotFormTestSubject>) => {
|
||||
const { find } = testBed;
|
||||
return {
|
||||
findDataStreamCallout() {
|
||||
return find('dataStreamWarningCallOut');
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type Actions = ReturnType<typeof setupActions>;
|
||||
|
||||
export type RestoreSnapshotTestBed = TestBed<RestoreSnapshotFormTestSubject> & {
|
||||
actions: Actions;
|
||||
};
|
||||
|
||||
export const setup = async (): Promise<RestoreSnapshotTestBed> => {
|
||||
const testBed = await initTestBed();
|
||||
|
||||
return {
|
||||
...testBed,
|
||||
actions: setupActions(testBed),
|
||||
};
|
||||
};
|
||||
|
||||
export type RestoreSnapshotFormTestSubject =
|
||||
| 'snapshotRestoreStepLogistics'
|
||||
| 'dataStreamWarningCallOut';
|
|
@ -64,6 +64,14 @@ export const setupEnvironment = () => {
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Suppress error messages about Worker not being available in JS DOM.
|
||||
*/
|
||||
(window as any).Worker = function Worker() {
|
||||
this.postMessage = () => {};
|
||||
this.terminate = () => {};
|
||||
};
|
||||
|
||||
export const WithAppDependencies = (Comp: any) => (props: any) => (
|
||||
<AppContextProvider value={appDependencies as any}>
|
||||
<Comp {...props} />
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
// import helpers first, this also sets up the mocks
|
||||
import { setupEnvironment, pageHelpers, nextTick, getRandomString } from './helpers';
|
||||
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import * as fixtures from '../../test/fixtures';
|
||||
|
||||
import { setupEnvironment, pageHelpers, nextTick, getRandomString } from './helpers';
|
||||
import { PolicyFormTestBed } from './helpers/policy_form.helpers';
|
||||
import { DEFAULT_POLICY_SCHEDULE } from '../../public/application/constants';
|
||||
|
||||
|
@ -37,7 +40,10 @@ describe('<PolicyAdd />', () => {
|
|||
describe('on component mount', () => {
|
||||
beforeEach(async () => {
|
||||
httpRequestsMockHelpers.setLoadRepositoriesResponse({ repositories: [repository] });
|
||||
httpRequestsMockHelpers.setLoadIndicesResponse({ indices: ['my_index'] });
|
||||
httpRequestsMockHelpers.setLoadIndicesResponse({
|
||||
indices: ['my_index'],
|
||||
dataStreams: ['my_data_stream', 'my_other_data_stream'],
|
||||
});
|
||||
|
||||
testBed = await setup();
|
||||
await nextTick();
|
||||
|
@ -96,7 +102,7 @@ describe('<PolicyAdd />', () => {
|
|||
actions.clickNextButton();
|
||||
});
|
||||
|
||||
test('should require at least one index', async () => {
|
||||
test('should require at least one index if no data streams are provided', async () => {
|
||||
const { find, form, component } = testBed;
|
||||
|
||||
await act(async () => {
|
||||
|
@ -109,7 +115,22 @@ describe('<PolicyAdd />', () => {
|
|||
// Deselect all indices from list
|
||||
find('deselectIndicesLink').simulate('click');
|
||||
|
||||
expect(form.getErrorsMessages()).toEqual(['You must select at least one index.']);
|
||||
expect(form.getErrorsMessages()).toEqual([
|
||||
'You must select at least one data stream or index.',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should correctly indicate data streams with a badge', async () => {
|
||||
const { find, component, form } = testBed;
|
||||
|
||||
await act(async () => {
|
||||
// Toggle "All indices" switch
|
||||
form.toggleEuiSwitch('allIndicesToggle', false);
|
||||
await nextTick();
|
||||
});
|
||||
component.update();
|
||||
|
||||
expect(find('dataStreamBadge').length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -35,7 +35,10 @@ describe('<PolicyEdit />', () => {
|
|||
describe('on component mount', () => {
|
||||
beforeEach(async () => {
|
||||
httpRequestsMockHelpers.setGetPolicyResponse({ policy: POLICY_EDIT });
|
||||
httpRequestsMockHelpers.setLoadIndicesResponse({ indices: ['my_index'] });
|
||||
httpRequestsMockHelpers.setLoadIndicesResponse({
|
||||
indices: ['my_index'],
|
||||
dataStreams: ['my_data_stream'],
|
||||
});
|
||||
httpRequestsMockHelpers.setLoadRepositoriesResponse({
|
||||
repositories: [{ name: POLICY_EDIT.repository }],
|
||||
});
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { nextTick, pageHelpers, setupEnvironment } from './helpers';
|
||||
import { RestoreSnapshotTestBed } from './helpers/restore_snapshot.helpers';
|
||||
import * as fixtures from '../../test/fixtures';
|
||||
|
||||
const {
|
||||
restoreSnapshot: { setup },
|
||||
} = pageHelpers;
|
||||
|
||||
describe('<RestoreSnapshot />', () => {
|
||||
const { server, httpRequestsMockHelpers } = setupEnvironment();
|
||||
let testBed: RestoreSnapshotTestBed;
|
||||
|
||||
afterAll(() => {
|
||||
server.restore();
|
||||
});
|
||||
describe('with data streams', () => {
|
||||
beforeEach(async () => {
|
||||
httpRequestsMockHelpers.setGetSnapshotResponse(fixtures.getSnapshot());
|
||||
testBed = await setup();
|
||||
await nextTick();
|
||||
testBed.component.update();
|
||||
});
|
||||
|
||||
it('shows the data streams warning when the snapshot has data streams', () => {
|
||||
const { exists } = testBed;
|
||||
expect(exists('dataStreamWarningCallOut')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('without data streams', () => {
|
||||
beforeEach(async () => {
|
||||
httpRequestsMockHelpers.setGetSnapshotResponse(fixtures.getSnapshot({ totalDataStreams: 0 }));
|
||||
testBed = await setup();
|
||||
await nextTick();
|
||||
testBed.component.update();
|
||||
});
|
||||
|
||||
it('hides the data streams warning when the snapshot has data streams', () => {
|
||||
const { exists } = testBed;
|
||||
expect(exists('dataStreamWarningCallOut')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -16,3 +16,5 @@ export {
|
|||
serializeSnapshotRetention,
|
||||
} from './snapshot_serialization';
|
||||
export { deserializePolicy, serializePolicy } from './policy_serialization';
|
||||
export { csvToArray } from './utils';
|
||||
export { isDataStreamBackingIndex } from './is_data_stream_backing_index';
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @remark
|
||||
* WARNING!
|
||||
*
|
||||
* This is a very hacky way of determining whether an index is a backing index.
|
||||
*
|
||||
* We only do this so that we can show users during a snapshot restore workflow
|
||||
* that an index is part of a data stream. At the moment there is no way for us
|
||||
* to get this information from the snapshot itself, even though it contains the
|
||||
* metadata for the data stream that information is fully opaque to us until after
|
||||
* we have done the snapshot restore.
|
||||
*
|
||||
* Issue for tracking this discussion here: https://github.com/elastic/elasticsearch/issues/58890
|
||||
*/
|
||||
export const isDataStreamBackingIndex = (indexName: string) => {
|
||||
return indexName.startsWith('.ds');
|
||||
};
|
|
@ -97,6 +97,7 @@ describe('deserializeSnapshotDetails', () => {
|
|||
version: 'version',
|
||||
// Indices are sorted.
|
||||
indices: ['index1', 'index2', 'index3'],
|
||||
dataStreams: [],
|
||||
includeGlobalState: false,
|
||||
// Failures are grouped and sorted by index, and the failures themselves are sorted by shard.
|
||||
indexFailures: [
|
||||
|
|
|
@ -17,6 +17,8 @@ import {
|
|||
|
||||
import { deserializeTime, serializeTime } from './time_serialization';
|
||||
|
||||
import { csvToArray } from './utils';
|
||||
|
||||
export function deserializeSnapshotDetails(
|
||||
repository: string,
|
||||
snapshotDetailsEs: SnapshotDetailsEs,
|
||||
|
@ -33,6 +35,7 @@ export function deserializeSnapshotDetails(
|
|||
version_id: versionId,
|
||||
version,
|
||||
indices = [],
|
||||
data_streams: dataStreams = [],
|
||||
include_global_state: includeGlobalState,
|
||||
state,
|
||||
start_time: startTime,
|
||||
|
@ -77,6 +80,7 @@ export function deserializeSnapshotDetails(
|
|||
versionId,
|
||||
version,
|
||||
indices: [...indices].sort(),
|
||||
dataStreams: [...dataStreams].sort(),
|
||||
includeGlobalState,
|
||||
state,
|
||||
startTime,
|
||||
|
@ -127,8 +131,10 @@ export function deserializeSnapshotConfig(snapshotConfigEs: SnapshotConfigEs): S
|
|||
export function serializeSnapshotConfig(snapshotConfig: SnapshotConfig): SnapshotConfigEs {
|
||||
const { indices, ignoreUnavailable, includeGlobalState, partial, metadata } = snapshotConfig;
|
||||
|
||||
const indicesArray = csvToArray(indices);
|
||||
|
||||
const snapshotConfigEs: SnapshotConfigEs = {
|
||||
indices,
|
||||
indices: indicesArray,
|
||||
ignore_unavailable: ignoreUnavailable,
|
||||
include_global_state: includeGlobalState,
|
||||
partial,
|
||||
|
|
13
x-pack/plugins/snapshot_restore/common/lib/utils.ts
Normal file
13
x-pack/plugins/snapshot_restore/common/lib/utils.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const csvToArray = (indices?: string | string[]): string[] => {
|
||||
return indices && Array.isArray(indices)
|
||||
? indices
|
||||
: typeof indices === 'string'
|
||||
? indices.split(',')
|
||||
: [];
|
||||
};
|
|
@ -8,3 +8,4 @@ export * from './repository';
|
|||
export * from './snapshot';
|
||||
export * from './restore';
|
||||
export * from './policy';
|
||||
export * from './indices';
|
||||
|
|
10
x-pack/plugins/snapshot_restore/common/types/indices.ts
Normal file
10
x-pack/plugins/snapshot_restore/common/types/indices.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface PolicyIndicesResponse {
|
||||
indices: string[];
|
||||
dataStreams: string[];
|
||||
}
|
|
@ -30,6 +30,7 @@ export interface SnapshotDetails {
|
|||
versionId: number;
|
||||
version: string;
|
||||
indices: string[];
|
||||
dataStreams: string[];
|
||||
includeGlobalState: boolean;
|
||||
state: string;
|
||||
/** e.g. '2019-04-05T21:56:40.438Z' */
|
||||
|
@ -52,6 +53,7 @@ export interface SnapshotDetailsEs {
|
|||
version_id: number;
|
||||
version: string;
|
||||
indices: string[];
|
||||
data_streams?: string[];
|
||||
include_global_state: boolean;
|
||||
state: string;
|
||||
/** e.g. '2019-04-05T21:56:40.438Z' */
|
||||
|
|
|
@ -1,76 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiTitle, EuiLink, EuiIcon, EuiText, EuiSpacer } from '@elastic/eui';
|
||||
interface Props {
|
||||
indices: string[] | string | undefined;
|
||||
}
|
||||
|
||||
export const CollapsibleIndicesList: React.FunctionComponent<Props> = ({ indices }) => {
|
||||
const [isShowingFullIndicesList, setIsShowingFullIndicesList] = useState<boolean>(false);
|
||||
const displayIndices = indices
|
||||
? typeof indices === 'string'
|
||||
? indices.split(',')
|
||||
: indices
|
||||
: undefined;
|
||||
const hiddenIndicesCount =
|
||||
displayIndices && displayIndices.length > 10 ? displayIndices.length - 10 : 0;
|
||||
return (
|
||||
<>
|
||||
{displayIndices ? (
|
||||
<>
|
||||
<EuiText>
|
||||
<ul>
|
||||
{(isShowingFullIndicesList ? displayIndices : [...displayIndices].splice(0, 10)).map(
|
||||
(index) => (
|
||||
<li key={index}>
|
||||
<EuiTitle size="xs">
|
||||
<span>{index}</span>
|
||||
</EuiTitle>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</EuiText>
|
||||
{hiddenIndicesCount ? (
|
||||
<>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiLink
|
||||
onClick={() =>
|
||||
isShowingFullIndicesList
|
||||
? setIsShowingFullIndicesList(false)
|
||||
: setIsShowingFullIndicesList(true)
|
||||
}
|
||||
>
|
||||
{isShowingFullIndicesList ? (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.indicesList.indicesCollapseAllLink"
|
||||
defaultMessage="Hide {count, plural, one {# index} other {# indices}}"
|
||||
values={{ count: hiddenIndicesCount }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.indicesList.indicesExpandAllLink"
|
||||
defaultMessage="Show {count, plural, one {# index} other {# indices}}"
|
||||
values={{ count: hiddenIndicesCount }}
|
||||
/>
|
||||
)}{' '}
|
||||
<EuiIcon type={isShowingFullIndicesList ? 'arrowUp' : 'arrowDown'} />
|
||||
</EuiLink>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.indicesList.allIndicesValue"
|
||||
defaultMessage="All indices"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiTitle, EuiLink, EuiIcon, EuiText, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { useCollapsibleList } from './use_collapsible_list';
|
||||
|
||||
interface Props {
|
||||
dataStreams: string[] | string | undefined;
|
||||
}
|
||||
|
||||
export const CollapsibleDataStreamsList: React.FunctionComponent<Props> = ({ dataStreams }) => {
|
||||
const { isShowingFullList, setIsShowingFullList, items, hiddenItemsCount } = useCollapsibleList({
|
||||
items: dataStreams,
|
||||
});
|
||||
|
||||
return items === 'all' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.dataStreamsList.allDataStreamsValue"
|
||||
defaultMessage="All data streams"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<EuiText>
|
||||
<ul>
|
||||
{items.map((dataStream) => (
|
||||
<li key={dataStream}>
|
||||
<EuiTitle size="xs">
|
||||
<span>{dataStream}</span>
|
||||
</EuiTitle>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</EuiText>
|
||||
{hiddenItemsCount ? (
|
||||
<>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiLink
|
||||
onClick={() =>
|
||||
isShowingFullList ? setIsShowingFullList(false) : setIsShowingFullList(true)
|
||||
}
|
||||
>
|
||||
{isShowingFullList ? (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.dataStreamsList.dataStreamsCollapseAllLink"
|
||||
defaultMessage="Hide {count, plural, one {# data stream} other {# data streams}}"
|
||||
values={{ count: hiddenItemsCount }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.dataStreamsList.dataStreamsExpandAllLink"
|
||||
defaultMessage="Show {count, plural, one {# data stream} other {# data streams}}"
|
||||
values={{ count: hiddenItemsCount }}
|
||||
/>
|
||||
)}{' '}
|
||||
<EuiIcon type={isShowingFullList ? 'arrowUp' : 'arrowDown'} />
|
||||
</EuiLink>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiTitle, EuiLink, EuiIcon, EuiText, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { useCollapsibleList } from './use_collapsible_list';
|
||||
|
||||
interface Props {
|
||||
indices: string[] | string | undefined;
|
||||
}
|
||||
|
||||
export const CollapsibleIndicesList: React.FunctionComponent<Props> = ({ indices }) => {
|
||||
const { hiddenItemsCount, isShowingFullList, items, setIsShowingFullList } = useCollapsibleList({
|
||||
items: indices,
|
||||
});
|
||||
return items === 'all' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.indicesList.allIndicesValue"
|
||||
defaultMessage="All indices"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<EuiText>
|
||||
<ul>
|
||||
{items.map((index) => (
|
||||
<li key={index}>
|
||||
<EuiTitle size="xs">
|
||||
<span>{index}</span>
|
||||
</EuiTitle>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</EuiText>
|
||||
{hiddenItemsCount ? (
|
||||
<>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiLink
|
||||
onClick={() =>
|
||||
isShowingFullList ? setIsShowingFullList(false) : setIsShowingFullList(true)
|
||||
}
|
||||
>
|
||||
{isShowingFullList ? (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.indicesList.indicesCollapseAllLink"
|
||||
defaultMessage="Hide {count, plural, one {# index} other {# indices}}"
|
||||
values={{ count: hiddenItemsCount }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.indicesList.indicesExpandAllLink"
|
||||
defaultMessage="Show {count, plural, one {# index} other {# indices}}"
|
||||
values={{ count: hiddenItemsCount }}
|
||||
/>
|
||||
)}{' '}
|
||||
<EuiIcon type={isShowingFullList ? 'arrowUp' : 'arrowDown'} />
|
||||
</EuiLink>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { CollapsibleIndicesList } from './collapsible_indices_list';
|
||||
export { CollapsibleDataStreamsList } from './collapsible_data_streams_list';
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import { useCollapsibleList } from './use_collapsible_list';
|
||||
|
||||
describe('useCollapseList', () => {
|
||||
it('handles undefined', () => {
|
||||
const { result } = renderHook(() => useCollapsibleList({ items: undefined }));
|
||||
expect(result.current.items).toBe('all');
|
||||
expect(result.current.hiddenItemsCount).toBe(0);
|
||||
});
|
||||
|
||||
it('handles csv', () => {
|
||||
const { result } = renderHook(() => useCollapsibleList({ items: 'a,b,c' }));
|
||||
expect(result.current.items).toEqual(['a', 'b', 'c']);
|
||||
expect(result.current.hiddenItemsCount).toBe(0);
|
||||
});
|
||||
|
||||
it('hides items passed a defined maximum (10)', () => {
|
||||
const items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'];
|
||||
const { result } = renderHook(() => useCollapsibleList({ items }));
|
||||
expect(result.current.items).toEqual(items.slice(0, -1));
|
||||
expect(result.current.hiddenItemsCount).toBe(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { csvToArray } from '../../../../common/lib';
|
||||
|
||||
type ChildItems = string[] | 'all';
|
||||
|
||||
interface Arg {
|
||||
items: string[] | string | undefined;
|
||||
}
|
||||
|
||||
export interface ReturnValue {
|
||||
items: ChildItems;
|
||||
hiddenItemsCount: number;
|
||||
isShowingFullList: boolean;
|
||||
setIsShowingFullList: (showAll: boolean) => void;
|
||||
}
|
||||
|
||||
const maximumItemPreviewCount = 10;
|
||||
|
||||
export const useCollapsibleList = ({ items }: Arg): ReturnValue => {
|
||||
const [isShowingFullList, setIsShowingFullList] = useState<boolean>(false);
|
||||
const itemsArray = csvToArray(items);
|
||||
const displayItems: ChildItems =
|
||||
items === undefined
|
||||
? 'all'
|
||||
: itemsArray.slice(0, isShowingFullList ? Infinity : maximumItemPreviewCount);
|
||||
|
||||
const hiddenItemsCount =
|
||||
itemsArray.length > maximumItemPreviewCount ? itemsArray.length - maximumItemPreviewCount : 0;
|
||||
|
||||
return {
|
||||
items: displayItems,
|
||||
hiddenItemsCount,
|
||||
setIsShowingFullList,
|
||||
isShowingFullList,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
|
||||
export const DataStreamBadge: FunctionComponent = () => {
|
||||
return (
|
||||
<EuiBadge data-test-subj="dataStreamBadge" color="primary">
|
||||
{i18n.translate('xpack.snapshotRestore.policyForm.setSettings.dataStreamBadgeContent', {
|
||||
defaultMessage: 'Data stream',
|
||||
})}
|
||||
</EuiBadge>
|
||||
);
|
||||
};
|
|
@ -15,7 +15,7 @@ export { SnapshotDeleteProvider } from './snapshot_delete_provider';
|
|||
export { RestoreSnapshotForm } from './restore_snapshot_form';
|
||||
export { PolicyExecuteProvider } from './policy_execute_provider';
|
||||
export { PolicyDeleteProvider } from './policy_delete_provider';
|
||||
export { CollapsibleIndicesList } from './collapsible_indices_list';
|
||||
export { CollapsibleIndicesList, CollapsibleDataStreamsList } from './collapsible_lists';
|
||||
export {
|
||||
RetentionSettingsUpdateModalProvider,
|
||||
UpdateRetentionSettings,
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const orderDataStreamsAndIndices = <D extends any>({
|
||||
dataStreams,
|
||||
indices,
|
||||
}: {
|
||||
dataStreams: D[];
|
||||
indices: D[];
|
||||
}) => {
|
||||
return dataStreams.concat(indices);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { orderDataStreamsAndIndices } from './helpers';
|
|
@ -27,6 +27,7 @@ import { PolicyNavigation } from './navigation';
|
|||
|
||||
interface Props {
|
||||
policy: SlmPolicyPayload;
|
||||
dataStreams: string[];
|
||||
indices: string[];
|
||||
currentUrl: string;
|
||||
isEditing?: boolean;
|
||||
|
@ -39,6 +40,7 @@ interface Props {
|
|||
|
||||
export const PolicyForm: React.FunctionComponent<Props> = ({
|
||||
policy: originalPolicy,
|
||||
dataStreams,
|
||||
indices,
|
||||
currentUrl,
|
||||
isEditing,
|
||||
|
@ -71,6 +73,8 @@ export const PolicyForm: React.FunctionComponent<Props> = ({
|
|||
},
|
||||
});
|
||||
|
||||
const isEditingManagedPolicy = Boolean(isEditing && policy.isManagedPolicy);
|
||||
|
||||
// Policy validation state
|
||||
const [validation, setValidation] = useState<PolicyValidation>({
|
||||
isValid: true,
|
||||
|
@ -132,6 +136,7 @@ export const PolicyForm: React.FunctionComponent<Props> = ({
|
|||
<CurrentStepForm
|
||||
policy={policy}
|
||||
indices={indices}
|
||||
dataStreams={dataStreams}
|
||||
updatePolicy={updatePolicy}
|
||||
isEditing={isEditing}
|
||||
currentUrl={currentUrl}
|
||||
|
@ -184,8 +189,8 @@ export const PolicyForm: React.FunctionComponent<Props> = ({
|
|||
{currentStep === lastStep ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill={isEditing && policy.isManagedPolicy ? false : true}
|
||||
color={isEditing && policy.isManagedPolicy ? 'warning' : 'secondary'}
|
||||
fill={!isEditingManagedPolicy}
|
||||
color={isEditingManagedPolicy ? 'warning' : 'secondary'}
|
||||
iconType="check"
|
||||
onClick={() => savePolicy()}
|
||||
isLoading={isSaving}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { PolicyValidation } from '../../../services/validation';
|
|||
export interface StepProps {
|
||||
policy: SlmPolicyPayload;
|
||||
indices: string[];
|
||||
dataStreams: string[];
|
||||
updatePolicy: (updatedSettings: Partial<SlmPolicyPayload>, validationHelperData?: any) => void;
|
||||
isEditing: boolean;
|
||||
currentUrl: string;
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
import { serializePolicy } from '../../../../../common/lib';
|
||||
import { useServices } from '../../../app_context';
|
||||
import { StepProps } from './';
|
||||
import { CollapsibleIndicesList } from '../../collapsible_indices_list';
|
||||
import { CollapsibleIndicesList } from '../../collapsible_lists';
|
||||
|
||||
export const PolicyStepReview: React.FunctionComponent<StepProps> = ({
|
||||
policy,
|
||||
|
@ -148,8 +148,8 @@ export const PolicyStepReview: React.FunctionComponent<StepProps> = ({
|
|||
<EuiDescriptionList textStyle="reverse">
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepReview.summaryTab.indicesLabel"
|
||||
defaultMessage="Indices"
|
||||
id="xpack.snapshotRestore.policyForm.stepReview.summaryTab.dataStreamsAndIndicesLabel"
|
||||
defaultMessage="Data streams and indices"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
|
@ -187,8 +187,8 @@ export const PolicyStepReview: React.FunctionComponent<StepProps> = ({
|
|||
<EuiDescriptionList textStyle="reverse">
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepReview.summaryTab.partialLabel"
|
||||
defaultMessage="Allow partial shards"
|
||||
id="xpack.snapshotRestore.policyForm.stepReview.summaryTab.partialIndicesLabel"
|
||||
defaultMessage="Allow partial indices"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
|
|
|
@ -1,469 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiDescribedFormGroup,
|
||||
EuiTitle,
|
||||
EuiFormRow,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiLink,
|
||||
EuiSelectable,
|
||||
EuiPanel,
|
||||
EuiComboBox,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { EuiSelectableOption } from '@elastic/eui';
|
||||
import { SlmPolicyPayload, SnapshotConfig } from '../../../../../common/types';
|
||||
import { documentationLinksService } from '../../../services/documentation';
|
||||
import { useServices } from '../../../app_context';
|
||||
import { StepProps } from './';
|
||||
|
||||
export const PolicyStepSettings: React.FunctionComponent<StepProps> = ({
|
||||
policy,
|
||||
indices,
|
||||
updatePolicy,
|
||||
errors,
|
||||
}) => {
|
||||
const { i18n } = useServices();
|
||||
const { config = {}, isManagedPolicy } = policy;
|
||||
|
||||
const updatePolicyConfig = (updatedFields: Partial<SlmPolicyPayload['config']>): void => {
|
||||
const newConfig = { ...config, ...updatedFields };
|
||||
updatePolicy({
|
||||
config: newConfig,
|
||||
});
|
||||
};
|
||||
|
||||
// States for choosing all indices, or a subset, including caching previously chosen subset list
|
||||
const [isAllIndices, setIsAllIndices] = useState<boolean>(!Boolean(config.indices));
|
||||
const [indicesSelection, setIndicesSelection] = useState<SnapshotConfig['indices']>([...indices]);
|
||||
const [indicesOptions, setIndicesOptions] = useState<EuiSelectableOption[]>(
|
||||
indices.map(
|
||||
(index): EuiSelectableOption => ({
|
||||
label: index,
|
||||
checked:
|
||||
isAllIndices ||
|
||||
// If indices is a string, we default to custom input mode, so we mark individual indices
|
||||
// as selected if user goes back to list mode
|
||||
typeof config.indices === 'string' ||
|
||||
(Array.isArray(config.indices) && config.indices.includes(index))
|
||||
? 'on'
|
||||
: undefined,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// State for using selectable indices list or custom patterns
|
||||
// Users with more than 100 indices will probably want to use an index pattern to select
|
||||
// them instead, so we'll default to showing them the index pattern input.
|
||||
const [selectIndicesMode, setSelectIndicesMode] = useState<'list' | 'custom'>(
|
||||
typeof config.indices === 'string' ||
|
||||
(Array.isArray(config.indices) && config.indices.length > 100)
|
||||
? 'custom'
|
||||
: 'list'
|
||||
);
|
||||
|
||||
// State for custom patterns
|
||||
const [indexPatterns, setIndexPatterns] = useState<string[]>(
|
||||
typeof config.indices === 'string' ? config.indices.split(',') : []
|
||||
);
|
||||
|
||||
const renderIndicesField = () => {
|
||||
const indicesSwitch = (
|
||||
<EuiSwitch
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.allIndicesLabel"
|
||||
defaultMessage="All indices, including system indices"
|
||||
/>
|
||||
}
|
||||
checked={isAllIndices}
|
||||
disabled={isManagedPolicy}
|
||||
data-test-subj="allIndicesToggle"
|
||||
onChange={(e) => {
|
||||
const isChecked = e.target.checked;
|
||||
setIsAllIndices(isChecked);
|
||||
if (isChecked) {
|
||||
updatePolicyConfig({ indices: undefined });
|
||||
} else {
|
||||
updatePolicyConfig({
|
||||
indices:
|
||||
selectIndicesMode === 'custom'
|
||||
? indexPatterns.join(',')
|
||||
: [...(indicesSelection || [])],
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.indicesTitle"
|
||||
defaultMessage="Indices"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.indicesDescription"
|
||||
defaultMessage="Indices to back up."
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow hasEmptyLabelSpace fullWidth>
|
||||
<Fragment>
|
||||
{isManagedPolicy ? (
|
||||
<EuiToolTip
|
||||
position="left"
|
||||
content={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.indicesTooltip"
|
||||
defaultMessage="Cloud-managed policies require all indices."
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
>
|
||||
{indicesSwitch}
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
indicesSwitch
|
||||
)}
|
||||
{isAllIndices ? null : (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
className="snapshotRestore__policyForm__stepSettings__indicesFieldWrapper"
|
||||
label={
|
||||
selectIndicesMode === 'list' ? (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.selectIndicesLabel"
|
||||
defaultMessage="Select indices"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
setSelectIndicesMode('custom');
|
||||
updatePolicyConfig({ indices: indexPatterns.join(',') });
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.indicesToggleCustomLink"
|
||||
defaultMessage="Use index patterns"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.indicesPatternLabel"
|
||||
defaultMessage="Index patterns"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
data-test-subj="selectIndicesLink"
|
||||
onClick={() => {
|
||||
setSelectIndicesMode('list');
|
||||
updatePolicyConfig({ indices: indicesSelection });
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.indicesToggleListLink"
|
||||
defaultMessage="Select indices"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)
|
||||
}
|
||||
helpText={
|
||||
selectIndicesMode === 'list' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.selectIndicesHelpText"
|
||||
defaultMessage="{count} {count, plural, one {index} other {indices}} will be backed up. {selectOrDeselectAllLink}"
|
||||
values={{
|
||||
count: config.indices && config.indices.length,
|
||||
selectOrDeselectAllLink:
|
||||
config.indices && config.indices.length > 0 ? (
|
||||
<EuiLink
|
||||
data-test-subj="deselectIndicesLink"
|
||||
onClick={() => {
|
||||
// TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed
|
||||
indicesOptions.forEach((option: EuiSelectableOption) => {
|
||||
option.checked = undefined;
|
||||
});
|
||||
updatePolicyConfig({ indices: [] });
|
||||
setIndicesSelection([]);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.deselectAllIndicesLink"
|
||||
defaultMessage="Deselect all"
|
||||
/>
|
||||
</EuiLink>
|
||||
) : (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
// TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed
|
||||
indicesOptions.forEach((option: EuiSelectableOption) => {
|
||||
option.checked = 'on';
|
||||
});
|
||||
updatePolicyConfig({ indices: [...indices] });
|
||||
setIndicesSelection([...indices]);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.selectAllIndicesLink"
|
||||
defaultMessage="Select all"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
isInvalid={Boolean(errors.indices)}
|
||||
error={errors.indices}
|
||||
>
|
||||
{selectIndicesMode === 'list' ? (
|
||||
<EuiSelectable
|
||||
allowExclusions={false}
|
||||
options={indicesOptions}
|
||||
onChange={(options) => {
|
||||
const newSelectedIndices: string[] = [];
|
||||
options.forEach(({ label, checked }) => {
|
||||
if (checked === 'on') {
|
||||
newSelectedIndices.push(label);
|
||||
}
|
||||
});
|
||||
setIndicesOptions(options);
|
||||
updatePolicyConfig({ indices: newSelectedIndices });
|
||||
setIndicesSelection(newSelectedIndices);
|
||||
}}
|
||||
searchable
|
||||
height={300}
|
||||
>
|
||||
{(list, search) => (
|
||||
<EuiPanel paddingSize="s" hasShadow={false}>
|
||||
{search}
|
||||
{list}
|
||||
</EuiPanel>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
) : (
|
||||
<EuiComboBox
|
||||
options={indices.map((index) => ({ label: index }))}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.snapshotRestore.policyForm.stepSettings.indicesPatternPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Enter index patterns, i.e. logstash-*',
|
||||
}
|
||||
)}
|
||||
selectedOptions={indexPatterns.map((pattern) => ({ label: pattern }))}
|
||||
onCreateOption={(pattern: string) => {
|
||||
if (!pattern.trim().length) {
|
||||
return;
|
||||
}
|
||||
const newPatterns = [...indexPatterns, pattern];
|
||||
setIndexPatterns(newPatterns);
|
||||
updatePolicyConfig({
|
||||
indices: newPatterns.join(','),
|
||||
});
|
||||
}}
|
||||
onChange={(patterns: Array<{ label: string }>) => {
|
||||
const newPatterns = patterns.map(({ label }) => label);
|
||||
setIndexPatterns(newPatterns);
|
||||
updatePolicyConfig({
|
||||
indices: newPatterns.join(','),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</EuiFormRow>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const renderIgnoreUnavailableField = () => (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.ignoreUnavailableDescriptionTitle"
|
||||
defaultMessage="Ignore unavailable indices"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.ignoreUnavailableDescription"
|
||||
defaultMessage="Ignores indices that are unavailable when taking the snapshot. Otherwise, the entire snapshot will fail."
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow hasEmptyLabelSpace fullWidth>
|
||||
<EuiSwitch
|
||||
data-test-subj="ignoreUnavailableIndicesToggle"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.ignoreUnavailableLabel"
|
||||
defaultMessage="Ignore unavailable indices"
|
||||
/>
|
||||
}
|
||||
checked={Boolean(config.ignoreUnavailable)}
|
||||
onChange={(e) => {
|
||||
updatePolicyConfig({
|
||||
ignoreUnavailable: e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
|
||||
const renderPartialField = () => (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.partialDescriptionTitle"
|
||||
defaultMessage="Allow partial indices"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.partialDescription"
|
||||
defaultMessage="Allows snapshots of indices with primary shards that are unavailable. Otherwise, the entire snapshot will fail."
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow hasEmptyLabelSpace fullWidth>
|
||||
<EuiSwitch
|
||||
data-test-subj="partialIndicesToggle"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.partialIndicesToggleSwitch"
|
||||
defaultMessage="Allow partial indices"
|
||||
/>
|
||||
}
|
||||
checked={Boolean(config.partial)}
|
||||
onChange={(e) => {
|
||||
updatePolicyConfig({
|
||||
partial: e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
|
||||
const renderIncludeGlobalStateField = () => (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.includeGlobalStateDescriptionTitle"
|
||||
defaultMessage="Include global state"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.includeGlobalStateDescription"
|
||||
defaultMessage="Stores the global state of the cluster as part of the snapshot."
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow hasEmptyLabelSpace fullWidth>
|
||||
<EuiSwitch
|
||||
data-test-subj="globalStateToggle"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.policyIncludeGlobalStateLabel"
|
||||
defaultMessage="Include global state"
|
||||
/>
|
||||
}
|
||||
checked={config.includeGlobalState === undefined || config.includeGlobalState}
|
||||
onChange={(e) => {
|
||||
updatePolicyConfig({
|
||||
includeGlobalState: e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
return (
|
||||
<div className="snapshotRestore__policyForm__stepSettings">
|
||||
{/* Step title and doc link */}
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettingsTitle"
|
||||
defaultMessage="Snapshot settings"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
flush="right"
|
||||
href={documentationLinksService.getSnapshotDocUrl()}
|
||||
target="_blank"
|
||||
iconType="help"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.docsButtonLabel"
|
||||
defaultMessage="Snapshot settings docs"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
{renderIndicesField()}
|
||||
{renderIgnoreUnavailableField()}
|
||||
{renderPartialField()}
|
||||
{renderIncludeGlobalStateField()}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { IndicesAndDataStreamsField } from './indices_and_data_streams_field';
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
|
||||
interface Props {
|
||||
onSelectionChange: (selection: 'all' | 'none') => void;
|
||||
selectedIndicesAndDataStreams: string[];
|
||||
indices: string[];
|
||||
dataStreams: string[];
|
||||
}
|
||||
|
||||
export const DataStreamsAndIndicesListHelpText: FunctionComponent<Props> = ({
|
||||
onSelectionChange,
|
||||
selectedIndicesAndDataStreams,
|
||||
indices,
|
||||
dataStreams,
|
||||
}) => {
|
||||
if (selectedIndicesAndDataStreams.length === 0) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.noDataStreamsOrIndicesHelpText"
|
||||
defaultMessage="Nothing will be backed up. {selectAllLink}"
|
||||
values={{
|
||||
selectAllLink: (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
onSelectionChange('all');
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.selectAllIndicesLink"
|
||||
defaultMessage="Select all"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const indicesCount = selectedIndicesAndDataStreams.reduce(
|
||||
(acc, v) => (indices.includes(v) ? acc + 1 : acc),
|
||||
0
|
||||
);
|
||||
const dataStreamsCount = selectedIndicesAndDataStreams.reduce(
|
||||
(acc, v) => (dataStreams.includes(v) ? acc + 1 : acc),
|
||||
0
|
||||
);
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.selectDataStreamsIndicesHelpText"
|
||||
defaultMessage="{indicesCount} {indicesCount, plural, one {index} other {indices}} and {dataStreamsCount} {dataStreamsCount, plural, one {data stream} other {data streams}} will be backed up. {deselectAllLink}"
|
||||
values={{
|
||||
dataStreamsCount,
|
||||
indicesCount,
|
||||
deselectAllLink: (
|
||||
<EuiLink
|
||||
data-test-subj="deselectIndicesLink"
|
||||
onClick={() => {
|
||||
onSelectionChange('none');
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.deselectAllIndicesLink"
|
||||
defaultMessage="Deselect all"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { determineListMode } from './helpers';
|
||||
|
||||
describe('helpers', () => {
|
||||
describe('determineListMode', () => {
|
||||
test('list length (> 100)', () => {
|
||||
expect(
|
||||
determineListMode({
|
||||
indices: Array.from(Array(101).keys()).map(String),
|
||||
dataStreams: [],
|
||||
configuredIndices: undefined,
|
||||
})
|
||||
).toBe('custom');
|
||||
|
||||
// The length of indices and data streams are cumulative
|
||||
expect(
|
||||
determineListMode({
|
||||
indices: Array.from(Array(51).keys()).map(String),
|
||||
dataStreams: Array.from(Array(51).keys()).map(String),
|
||||
configuredIndices: undefined,
|
||||
})
|
||||
).toBe('custom');
|
||||
|
||||
// Other values should result in list mode
|
||||
expect(
|
||||
determineListMode({
|
||||
indices: [],
|
||||
dataStreams: [],
|
||||
configuredIndices: undefined,
|
||||
})
|
||||
).toBe('list');
|
||||
});
|
||||
|
||||
test('configured indices is a string', () => {
|
||||
expect(
|
||||
determineListMode({
|
||||
indices: [],
|
||||
dataStreams: [],
|
||||
configuredIndices: 'test',
|
||||
})
|
||||
).toBe('custom');
|
||||
});
|
||||
|
||||
test('configured indices not included in current indices and data streams', () => {
|
||||
expect(
|
||||
determineListMode({
|
||||
indices: ['a'],
|
||||
dataStreams: ['b'],
|
||||
configuredIndices: ['a', 'b', 'c'],
|
||||
})
|
||||
).toBe('custom');
|
||||
});
|
||||
|
||||
test('configured indices included in current indices and data streams', () => {
|
||||
expect(
|
||||
determineListMode({
|
||||
indices: ['a'],
|
||||
dataStreams: ['b'],
|
||||
configuredIndices: ['a', 'b'],
|
||||
})
|
||||
).toBe('list');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiSelectableOption } from '@elastic/eui';
|
||||
import { orderDataStreamsAndIndices } from '../../../../../lib';
|
||||
import { DataStreamBadge } from '../../../../../data_stream_badge';
|
||||
|
||||
export const mapSelectionToIndicesOptions = ({
|
||||
allSelected,
|
||||
selection,
|
||||
dataStreams,
|
||||
indices,
|
||||
}: {
|
||||
allSelected: boolean;
|
||||
selection: string[];
|
||||
dataStreams: string[];
|
||||
indices: string[];
|
||||
}): EuiSelectableOption[] => {
|
||||
return orderDataStreamsAndIndices<EuiSelectableOption>({
|
||||
dataStreams: dataStreams.map(
|
||||
(dataStream): EuiSelectableOption => {
|
||||
return {
|
||||
label: dataStream,
|
||||
append: <DataStreamBadge />,
|
||||
checked: allSelected || selection.includes(dataStream) ? 'on' : undefined,
|
||||
};
|
||||
}
|
||||
),
|
||||
indices: indices.map(
|
||||
(index): EuiSelectableOption => {
|
||||
return {
|
||||
label: index,
|
||||
checked: allSelected || selection.includes(index) ? 'on' : undefined,
|
||||
};
|
||||
}
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @remark
|
||||
* Users with more than 100 indices will probably want to use an index pattern to select
|
||||
* them instead, so we'll default to showing them the index pattern input. Also show the custom
|
||||
* list if we have no exact matches in the configured array to some existing index.
|
||||
*/
|
||||
export const determineListMode = ({
|
||||
configuredIndices,
|
||||
indices,
|
||||
dataStreams,
|
||||
}: {
|
||||
configuredIndices: string | string[] | undefined;
|
||||
indices: string[];
|
||||
dataStreams: string[];
|
||||
}): 'custom' | 'list' => {
|
||||
const indicesAndDataStreams = indices.concat(dataStreams);
|
||||
return typeof configuredIndices === 'string' ||
|
||||
indicesAndDataStreams.length > 100 ||
|
||||
(Array.isArray(configuredIndices) &&
|
||||
// If not every past configured index maps to an existing index or data stream
|
||||
// we also show the custom list
|
||||
!configuredIndices.every((c) => indicesAndDataStreams.some((i) => i === c)))
|
||||
? 'custom'
|
||||
: 'list';
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { IndicesAndDataStreamsField } from './indices_and_data_streams_field';
|
|
@ -0,0 +1,348 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, FunctionComponent, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import {
|
||||
EuiComboBox,
|
||||
EuiDescribedFormGroup,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiLink,
|
||||
EuiPanel,
|
||||
EuiSelectable,
|
||||
EuiSelectableOption,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiTitle,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { SlmPolicyPayload } from '../../../../../../../../common/types';
|
||||
import { useServices } from '../../../../../../app_context';
|
||||
import { PolicyValidation } from '../../../../../../services/validation';
|
||||
|
||||
import { orderDataStreamsAndIndices } from '../../../../../lib';
|
||||
import { DataStreamBadge } from '../../../../../data_stream_badge';
|
||||
|
||||
import { mapSelectionToIndicesOptions, determineListMode } from './helpers';
|
||||
|
||||
import { DataStreamsAndIndicesListHelpText } from './data_streams_and_indices_list_help_text';
|
||||
|
||||
interface Props {
|
||||
isManagedPolicy: boolean;
|
||||
policy: SlmPolicyPayload;
|
||||
indices: string[];
|
||||
dataStreams: string[];
|
||||
onUpdate: (arg: { indices?: string[] | string }) => void;
|
||||
errors: PolicyValidation['errors'];
|
||||
}
|
||||
|
||||
/**
|
||||
* In future we may be able to split data streams to its own field, but for now
|
||||
* they share an array "indices" in the snapshot lifecycle policy config. See
|
||||
* this github issue for progress: https://github.com/elastic/elasticsearch/issues/58474
|
||||
*/
|
||||
export const IndicesAndDataStreamsField: FunctionComponent<Props> = ({
|
||||
isManagedPolicy,
|
||||
dataStreams,
|
||||
indices,
|
||||
policy,
|
||||
onUpdate,
|
||||
errors,
|
||||
}) => {
|
||||
const { i18n } = useServices();
|
||||
const { config = {} } = policy;
|
||||
|
||||
const indicesAndDataStreams = indices.concat(dataStreams);
|
||||
|
||||
// We assume all indices if the config has no indices entry or if we receive an empty array
|
||||
const [isAllIndices, setIsAllIndices] = useState<boolean>(
|
||||
!config.indices || (Array.isArray(config.indices) && config.indices.length === 0)
|
||||
);
|
||||
|
||||
const [indicesAndDataStreamsSelection, setIndicesAndDataStreamsSelection] = useState<string[]>(
|
||||
() =>
|
||||
Array.isArray(config.indices) && !isAllIndices
|
||||
? indicesAndDataStreams.filter((i) => (config.indices! as string[]).includes(i))
|
||||
: [...indicesAndDataStreams]
|
||||
);
|
||||
|
||||
// States for choosing all indices, or a subset, including caching previously chosen subset list
|
||||
const [indicesAndDataStreamsOptions, setIndicesAndDataStreamsOptions] = useState<
|
||||
EuiSelectableOption[]
|
||||
>(() =>
|
||||
mapSelectionToIndicesOptions({
|
||||
selection: indicesAndDataStreamsSelection,
|
||||
dataStreams,
|
||||
indices,
|
||||
allSelected: isAllIndices || typeof config.indices === 'string',
|
||||
})
|
||||
);
|
||||
|
||||
// State for using selectable indices list or custom patterns
|
||||
const [selectIndicesMode, setSelectIndicesMode] = useState<'list' | 'custom'>(() =>
|
||||
determineListMode({ configuredIndices: config.indices, dataStreams, indices })
|
||||
);
|
||||
|
||||
// State for custom patterns
|
||||
const [indexPatterns, setIndexPatterns] = useState<string[]>(() =>
|
||||
typeof config.indices === 'string'
|
||||
? (config.indices as string).split(',')
|
||||
: Array.isArray(config.indices) && config.indices
|
||||
? config.indices
|
||||
: []
|
||||
);
|
||||
|
||||
const indicesSwitch = (
|
||||
<EuiSwitch
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.allDataStreamsAndIndicesLabel"
|
||||
defaultMessage="All data streams and indices, including system indices"
|
||||
/>
|
||||
}
|
||||
checked={isAllIndices}
|
||||
disabled={isManagedPolicy}
|
||||
data-test-subj="allIndicesToggle"
|
||||
onChange={(e) => {
|
||||
const isChecked = e.target.checked;
|
||||
setIsAllIndices(isChecked);
|
||||
if (isChecked) {
|
||||
setIndicesAndDataStreamsSelection(indicesAndDataStreams);
|
||||
setIndicesAndDataStreamsOptions(
|
||||
mapSelectionToIndicesOptions({
|
||||
allSelected: isAllIndices || typeof config.indices === 'string',
|
||||
dataStreams,
|
||||
indices,
|
||||
selection: indicesAndDataStreamsSelection,
|
||||
})
|
||||
);
|
||||
onUpdate({ indices: undefined });
|
||||
} else {
|
||||
onUpdate({
|
||||
indices:
|
||||
selectIndicesMode === 'custom'
|
||||
? indexPatterns.join(',')
|
||||
: [...(indicesAndDataStreamsSelection || [])],
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.dataStreamsAndIndicesTitle"
|
||||
defaultMessage="Data streams and indices"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.dataStreamsAndIndicesDescription"
|
||||
defaultMessage="To back up indices and data streams, manually select them or define index patterns to dynamically match them."
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow hasEmptyLabelSpace fullWidth>
|
||||
<Fragment>
|
||||
{isManagedPolicy ? (
|
||||
<EuiToolTip
|
||||
position="left"
|
||||
content={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.indicesTooltip"
|
||||
defaultMessage="Cloud-managed policies require all indices."
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
>
|
||||
{indicesSwitch}
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
indicesSwitch
|
||||
)}
|
||||
{isAllIndices ? null : (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
className="snapshotRestore__policyForm__stepSettings__indicesFieldWrapper"
|
||||
label={
|
||||
selectIndicesMode === 'list' ? (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.selectIndicesLabel"
|
||||
defaultMessage="Select indices and data streams"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
setSelectIndicesMode('custom');
|
||||
onUpdate({ indices: indexPatterns.join(',') });
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.indicesToggleCustomLink"
|
||||
defaultMessage="Use index patterns"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.indicesPatternLabel"
|
||||
defaultMessage="Index patterns"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
data-test-subj="selectIndicesLink"
|
||||
onClick={() => {
|
||||
setSelectIndicesMode('list');
|
||||
onUpdate({ indices: indicesAndDataStreamsSelection });
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.dataStreamsAndIndicesToggleListLink"
|
||||
defaultMessage="Select data streams and indices"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)
|
||||
}
|
||||
helpText={
|
||||
selectIndicesMode === 'list' ? (
|
||||
<DataStreamsAndIndicesListHelpText
|
||||
onSelectionChange={(selection) => {
|
||||
if (selection === 'all') {
|
||||
// TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed
|
||||
indicesAndDataStreamsOptions.forEach((option: EuiSelectableOption) => {
|
||||
option.checked = 'on';
|
||||
});
|
||||
onUpdate({ indices: [...indicesAndDataStreams] });
|
||||
setIndicesAndDataStreamsSelection([...indicesAndDataStreams]);
|
||||
} else {
|
||||
// TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed
|
||||
indicesAndDataStreamsOptions.forEach((option: EuiSelectableOption) => {
|
||||
option.checked = undefined;
|
||||
});
|
||||
onUpdate({ indices: [] });
|
||||
setIndicesAndDataStreamsSelection([]);
|
||||
}
|
||||
}}
|
||||
selectedIndicesAndDataStreams={indicesAndDataStreamsSelection}
|
||||
indices={indices}
|
||||
dataStreams={dataStreams}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
isInvalid={Boolean(errors.indices)}
|
||||
error={errors.indices}
|
||||
>
|
||||
{selectIndicesMode === 'list' ? (
|
||||
<EuiSelectable
|
||||
allowExclusions={false}
|
||||
data-test-subj="indicesAndDataStreamsList"
|
||||
options={indicesAndDataStreamsOptions}
|
||||
onChange={(options) => {
|
||||
const newSelectedIndices: string[] = [];
|
||||
options.forEach(({ label, checked }) => {
|
||||
if (checked === 'on') {
|
||||
newSelectedIndices.push(label);
|
||||
}
|
||||
});
|
||||
setIndicesAndDataStreamsOptions(options);
|
||||
onUpdate({ indices: newSelectedIndices });
|
||||
setIndicesAndDataStreamsSelection(newSelectedIndices);
|
||||
}}
|
||||
searchable
|
||||
height={300}
|
||||
>
|
||||
{(list, search) => (
|
||||
<EuiPanel paddingSize="s" hasShadow={false}>
|
||||
{search}
|
||||
{list}
|
||||
</EuiPanel>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
) : (
|
||||
<EuiComboBox
|
||||
options={orderDataStreamsAndIndices({
|
||||
indices: indices.map((index) => ({
|
||||
label: index,
|
||||
value: { isDataStream: false },
|
||||
})),
|
||||
dataStreams: dataStreams.map((dataStream) => ({
|
||||
label: dataStream,
|
||||
value: { isDataStream: true },
|
||||
})),
|
||||
})}
|
||||
renderOption={({ label, value }) => {
|
||||
if (value?.isDataStream) {
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
responsive={false}
|
||||
justifyContent="spaceBetween"
|
||||
alignItems="center"
|
||||
>
|
||||
<EuiFlexItem grow={false}>{label}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<DataStreamBadge />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
return label;
|
||||
}}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.snapshotRestore.policyForm.stepSettings.indicesPatternPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Enter index patterns, i.e. logstash-*',
|
||||
}
|
||||
)}
|
||||
selectedOptions={indexPatterns.map((pattern) => ({ label: pattern }))}
|
||||
onCreateOption={(pattern: string) => {
|
||||
if (!pattern.trim().length) {
|
||||
return;
|
||||
}
|
||||
const newPatterns = [...indexPatterns, pattern];
|
||||
setIndexPatterns(newPatterns);
|
||||
onUpdate({
|
||||
indices: newPatterns.join(','),
|
||||
});
|
||||
}}
|
||||
onChange={(patterns: Array<{ label: string }>) => {
|
||||
const newPatterns = patterns.map(({ label }) => label);
|
||||
setIndexPatterns(newPatterns);
|
||||
onUpdate({
|
||||
indices: newPatterns.join(','),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</EuiFormRow>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { PolicyStepSettings } from './step_settings';
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiDescribedFormGroup,
|
||||
EuiTitle,
|
||||
EuiFormRow,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { SlmPolicyPayload } from '../../../../../../common/types';
|
||||
import { documentationLinksService } from '../../../../services/documentation';
|
||||
import { StepProps } from '../';
|
||||
|
||||
import { IndicesAndDataStreamsField } from './fields';
|
||||
|
||||
export const PolicyStepSettings: React.FunctionComponent<StepProps> = ({
|
||||
policy,
|
||||
indices,
|
||||
dataStreams,
|
||||
updatePolicy,
|
||||
errors,
|
||||
}) => {
|
||||
const { config = {}, isManagedPolicy } = policy;
|
||||
|
||||
const updatePolicyConfig = (updatedFields: Partial<SlmPolicyPayload['config']>): void => {
|
||||
const newConfig = { ...config, ...updatedFields };
|
||||
updatePolicy({
|
||||
config: newConfig,
|
||||
});
|
||||
};
|
||||
|
||||
const renderIgnoreUnavailableField = () => (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.ignoreUnavailableDescriptionTitle"
|
||||
defaultMessage="Ignore unavailable indices"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.ignoreUnavailableDescription"
|
||||
defaultMessage="Ignores indices that are unavailable when taking the snapshot. Otherwise, the entire snapshot will fail."
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow hasEmptyLabelSpace fullWidth>
|
||||
<EuiSwitch
|
||||
data-test-subj="ignoreUnavailableIndicesToggle"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.ignoreUnavailableLabel"
|
||||
defaultMessage="Ignore unavailable indices"
|
||||
/>
|
||||
}
|
||||
checked={Boolean(config.ignoreUnavailable)}
|
||||
onChange={(e) => {
|
||||
updatePolicyConfig({
|
||||
ignoreUnavailable: e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
|
||||
const renderPartialField = () => (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.partialDescriptionTitle"
|
||||
defaultMessage="Allow partial indices"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.partialDescription"
|
||||
defaultMessage="Allows snapshots of indices with primary shards that are unavailable. Otherwise, the entire snapshot will fail."
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow hasEmptyLabelSpace fullWidth>
|
||||
<EuiSwitch
|
||||
data-test-subj="partialIndicesToggle"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.partialIndicesToggleSwitch"
|
||||
defaultMessage="Allow partial indices"
|
||||
/>
|
||||
}
|
||||
checked={Boolean(config.partial)}
|
||||
onChange={(e) => {
|
||||
updatePolicyConfig({
|
||||
partial: e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
|
||||
const renderIncludeGlobalStateField = () => (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.includeGlobalStateDescriptionTitle"
|
||||
defaultMessage="Include global state"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.includeGlobalStateDescription"
|
||||
defaultMessage="Stores the global cluster state as part of the snapshot."
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow hasEmptyLabelSpace fullWidth>
|
||||
<EuiSwitch
|
||||
data-test-subj="globalStateToggle"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.policyIncludeGlobalStateLabel"
|
||||
defaultMessage="Include global state"
|
||||
/>
|
||||
}
|
||||
checked={config.includeGlobalState === undefined || config.includeGlobalState}
|
||||
onChange={(e) => {
|
||||
updatePolicyConfig({
|
||||
includeGlobalState: e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
return (
|
||||
<div className="snapshotRestore__policyForm__stepSettings">
|
||||
{/* Step title and doc link */}
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettingsTitle"
|
||||
defaultMessage="Snapshot settings"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
flush="right"
|
||||
href={documentationLinksService.getSnapshotDocUrl()}
|
||||
target="_blank"
|
||||
iconType="help"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyForm.stepSettings.docsButtonLabel"
|
||||
defaultMessage="Snapshot settings docs"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<IndicesAndDataStreamsField
|
||||
isManagedPolicy={isManagedPolicy}
|
||||
errors={errors}
|
||||
dataStreams={dataStreams}
|
||||
indices={indices}
|
||||
policy={policy}
|
||||
onUpdate={updatePolicyConfig}
|
||||
/>
|
||||
|
||||
{renderIgnoreUnavailableField()}
|
||||
{renderPartialField()}
|
||||
{renderIncludeGlobalStateField()}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -14,6 +14,6 @@ export interface StepProps {
|
|||
updateCurrentStep: (step: number) => void;
|
||||
}
|
||||
|
||||
export { RestoreSnapshotStepLogistics } from './step_logistics';
|
||||
export { RestoreSnapshotStepLogistics } from './step_logistics/step_logistics';
|
||||
export { RestoreSnapshotStepSettings } from './step_settings';
|
||||
export { RestoreSnapshotStepReview } from './step_review';
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
|
||||
interface Props {
|
||||
onSelectionChange: (selection: 'all' | 'none') => void;
|
||||
selectedIndicesAndDataStreams: string[];
|
||||
indices: string[];
|
||||
dataStreams: string[];
|
||||
}
|
||||
|
||||
export const DataStreamsAndIndicesListHelpText: FunctionComponent<Props> = ({
|
||||
onSelectionChange,
|
||||
selectedIndicesAndDataStreams,
|
||||
indices,
|
||||
dataStreams,
|
||||
}) => {
|
||||
if (selectedIndicesAndDataStreams.length === 0) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.noDataStreamsOrIndicesHelpText"
|
||||
defaultMessage="Nothing will be restored. {selectAllLink}"
|
||||
values={{
|
||||
selectAllLink: (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
onSelectionChange('all');
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.selectAllIndicesLink"
|
||||
defaultMessage="Select all"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const indicesCount = selectedIndicesAndDataStreams.reduce(
|
||||
(acc, v) => (indices.includes(v) ? acc + 1 : acc),
|
||||
0
|
||||
);
|
||||
const dataStreamsCount = selectedIndicesAndDataStreams.reduce(
|
||||
(acc, v) => (dataStreams.includes(v) ? acc + 1 : acc),
|
||||
0
|
||||
);
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.selectDataStreamsAndIndicesHelpText"
|
||||
defaultMessage="{indicesCount} {indicesCount, plural, one {index} other {indices}} and {dataStreamsCount} {dataStreamsCount, plural, one {data stream} other {data streams}} will be restored. {deselectAllLink}"
|
||||
values={{
|
||||
dataStreamsCount,
|
||||
indicesCount,
|
||||
deselectAllLink: (
|
||||
<EuiLink
|
||||
data-test-subj="deselectIndicesLink"
|
||||
onClick={() => {
|
||||
onSelectionChange('none');
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.deselectAllIndicesLink"
|
||||
defaultMessage="Deselect all"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { EuiCallOut, EuiLink } from '@elastic/eui';
|
||||
|
||||
import { documentationLinksService } from '../../../../services/documentation';
|
||||
|
||||
const i18nTexts = {
|
||||
callout: {
|
||||
title: (count: number) =>
|
||||
i18n.translate('xpack.snapshotRestore.restoreForm.dataStreamsWarningCallOut.title', {
|
||||
defaultMessage:
|
||||
'This snapshot contains {count, plural, one {a data stream} other {data streams}}',
|
||||
values: { count },
|
||||
}),
|
||||
body: () => (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.dataStreamsWarningCallOut.body"
|
||||
defaultMessage="Each data stream requires a matching index template. Please ensure any restored data streams have a matching index template. You can restore index templates by restoring the global cluster state. However, this may overwrite existing templates, cluster settings, ingest pipelines, and lifecycle policies. {learnMoreLink} about restoring snapshots that contain data streams."
|
||||
values={{
|
||||
learnMoreLink: (
|
||||
<EuiLink target="_blank" href={documentationLinksService.getSnapshotDocUrl()}>
|
||||
{i18n.translate(
|
||||
'xpack.snapshotRestore.restoreForm.dataStreamsWarningCallOut.body.learnMoreLink',
|
||||
{ defaultMessage: 'Learn more' }
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
interface Props {
|
||||
dataStreamsCount: number;
|
||||
}
|
||||
|
||||
export const DataStreamsGlobalStateCallOut: FunctionComponent<Props> = ({ dataStreamsCount }) => {
|
||||
return (
|
||||
<EuiCallOut
|
||||
data-test-subj="dataStreamWarningCallOut"
|
||||
title={i18nTexts.callout.title(dataStreamsCount)}
|
||||
iconType="alert"
|
||||
color="warning"
|
||||
>
|
||||
{i18nTexts.callout.body()}
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { RestoreSnapshotStepLogistics } from './step_logistics';
|
|
@ -21,10 +21,22 @@ import {
|
|||
EuiComboBox,
|
||||
} from '@elastic/eui';
|
||||
import { EuiSelectableOption } from '@elastic/eui';
|
||||
import { RestoreSettings } from '../../../../../common/types';
|
||||
import { documentationLinksService } from '../../../services/documentation';
|
||||
import { useServices } from '../../../app_context';
|
||||
import { StepProps } from './';
|
||||
|
||||
import { csvToArray, isDataStreamBackingIndex } from '../../../../../../common/lib';
|
||||
import { RestoreSettings } from '../../../../../../common/types';
|
||||
|
||||
import { documentationLinksService } from '../../../../services/documentation';
|
||||
|
||||
import { useServices } from '../../../../app_context';
|
||||
|
||||
import { orderDataStreamsAndIndices } from '../../../lib';
|
||||
import { DataStreamBadge } from '../../../data_stream_badge';
|
||||
|
||||
import { StepProps } from '../index';
|
||||
|
||||
import { DataStreamsGlobalStateCallOut } from './data_streams_global_state_call_out';
|
||||
|
||||
import { DataStreamsAndIndicesListHelpText } from './data_streams_and_indices_list_help_text';
|
||||
|
||||
export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> = ({
|
||||
snapshotDetails,
|
||||
|
@ -34,10 +46,30 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
}) => {
|
||||
const { i18n } = useServices();
|
||||
const {
|
||||
indices: snapshotIndices,
|
||||
indices: unfilteredSnapshotIndices,
|
||||
dataStreams: snapshotDataStreams = [],
|
||||
includeGlobalState: snapshotIncludeGlobalState,
|
||||
} = snapshotDetails;
|
||||
|
||||
const snapshotIndices = unfilteredSnapshotIndices.filter(
|
||||
(index) => !isDataStreamBackingIndex(index)
|
||||
);
|
||||
const snapshotIndicesAndDataStreams = snapshotIndices.concat(snapshotDataStreams);
|
||||
|
||||
const comboBoxOptions = orderDataStreamsAndIndices<{
|
||||
label: string;
|
||||
value: { isDataStream: boolean; name: string };
|
||||
}>({
|
||||
dataStreams: snapshotDataStreams.map((dataStream) => ({
|
||||
label: dataStream,
|
||||
value: { isDataStream: true, name: dataStream },
|
||||
})),
|
||||
indices: snapshotIndices.map((index) => ({
|
||||
label: index,
|
||||
value: { isDataStream: false, name: index },
|
||||
})),
|
||||
});
|
||||
|
||||
const {
|
||||
indices: restoreIndices,
|
||||
renamePattern,
|
||||
|
@ -47,13 +79,32 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
} = restoreSettings;
|
||||
|
||||
// States for choosing all indices, or a subset, including caching previously chosen subset list
|
||||
const [isAllIndices, setIsAllIndices] = useState<boolean>(!Boolean(restoreIndices));
|
||||
const [indicesOptions, setIndicesOptions] = useState<EuiSelectableOption[]>(
|
||||
snapshotIndices.map(
|
||||
const [isAllIndicesAndDataStreams, setIsAllIndicesAndDataStreams] = useState<boolean>(
|
||||
!Boolean(restoreIndices)
|
||||
);
|
||||
const [indicesAndDataStreamsOptions, setIndicesAndDataStreamsOptions] = useState<
|
||||
EuiSelectableOption[]
|
||||
>(() =>
|
||||
orderDataStreamsAndIndices({
|
||||
dataStreams: snapshotDataStreams.map(
|
||||
(dataStream): EuiSelectableOption => ({
|
||||
label: dataStream,
|
||||
append: <DataStreamBadge />,
|
||||
checked:
|
||||
isAllIndicesAndDataStreams ||
|
||||
// If indices is a string, we default to custom input mode, so we mark individual indices
|
||||
// as selected if user goes back to list mode
|
||||
typeof restoreIndices === 'string' ||
|
||||
(Array.isArray(restoreIndices) && restoreIndices.includes(dataStream))
|
||||
? 'on'
|
||||
: undefined,
|
||||
})
|
||||
),
|
||||
indices: snapshotIndices.map(
|
||||
(index): EuiSelectableOption => ({
|
||||
label: index,
|
||||
checked:
|
||||
isAllIndices ||
|
||||
isAllIndicesAndDataStreams ||
|
||||
// If indices is a string, we default to custom input mode, so we mark individual indices
|
||||
// as selected if user goes back to list mode
|
||||
typeof restoreIndices === 'string' ||
|
||||
|
@ -61,14 +112,17 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
? 'on'
|
||||
: undefined,
|
||||
})
|
||||
)
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
// State for using selectable indices list or custom patterns
|
||||
// Users with more than 100 indices will probably want to use an index pattern to select
|
||||
// them instead, so we'll default to showing them the index pattern input.
|
||||
const [selectIndicesMode, setSelectIndicesMode] = useState<'list' | 'custom'>(
|
||||
typeof restoreIndices === 'string' || snapshotIndices.length > 100 ? 'custom' : 'list'
|
||||
typeof restoreIndices === 'string' || snapshotIndicesAndDataStreams.length > 100
|
||||
? 'custom'
|
||||
: 'list'
|
||||
);
|
||||
|
||||
// State for custom patterns
|
||||
|
@ -83,13 +137,16 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
|
||||
// Caching state for togglable settings
|
||||
const [cachedRestoreSettings, setCachedRestoreSettings] = useState<RestoreSettings>({
|
||||
indices: [...snapshotIndices],
|
||||
indices: [...snapshotIndicesAndDataStreams],
|
||||
renamePattern: '',
|
||||
renameReplacement: '',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="snapshotRestore__restoreForm__stepLogistics">
|
||||
<div
|
||||
data-test-subj="snapshotRestoreStepLogistics"
|
||||
className="snapshotRestore__restoreForm__stepLogistics"
|
||||
>
|
||||
{/* Step title and doc link */}
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -118,6 +175,14 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{snapshotDataStreams.length ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<DataStreamsGlobalStateCallOut dataStreamsCount={snapshotDataStreams.length} />
|
||||
</>
|
||||
) : undefined}
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
{/* Indices */}
|
||||
|
@ -126,16 +191,16 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.indicesTitle"
|
||||
defaultMessage="Indices"
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.dataStreamsAndIndicesTitle"
|
||||
defaultMessage="Data streams and indices"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.indicesDescription"
|
||||
defaultMessage="Creates new indices if they don’t exist. Restores existing indices
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.dataStreamsAndIndicesDescription"
|
||||
defaultMessage="Creates new data streams and indices if they don’t exist. Opens existing indices, including backing indices for a data stream,
|
||||
if they are closed and have the same number of shards as the snapshot index."
|
||||
/>
|
||||
}
|
||||
|
@ -146,14 +211,14 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
<EuiSwitch
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.allIndicesLabel"
|
||||
defaultMessage="All indices, including system indices"
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.allDataStreamsAndIndicesLabel"
|
||||
defaultMessage="All data streams and indices, including system indices"
|
||||
/>
|
||||
}
|
||||
checked={isAllIndices}
|
||||
checked={isAllIndicesAndDataStreams}
|
||||
onChange={(e) => {
|
||||
const isChecked = e.target.checked;
|
||||
setIsAllIndices(isChecked);
|
||||
setIsAllIndicesAndDataStreams(isChecked);
|
||||
if (isChecked) {
|
||||
updateRestoreSettings({ indices: undefined });
|
||||
} else {
|
||||
|
@ -166,7 +231,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
}
|
||||
}}
|
||||
/>
|
||||
{isAllIndices ? null : (
|
||||
{isAllIndicesAndDataStreams ? null : (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
|
@ -176,8 +241,8 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.selectIndicesLabel"
|
||||
defaultMessage="Select indices"
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.selectDataStreamsAndIndicesLabel"
|
||||
defaultMessage="Select data streams and indices"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -210,8 +275,8 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.indicesToggleListLink"
|
||||
defaultMessage="Select indices"
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.dataStreamsAndIndicesToggleListLink"
|
||||
defaultMessage="Select data streams and indices"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
|
@ -220,17 +285,23 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
}
|
||||
helpText={
|
||||
selectIndicesMode === 'list' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.selectIndicesHelpText"
|
||||
defaultMessage="{count} {count, plural, one {index} other {indices}} will be restored. {selectOrDeselectAllLink}"
|
||||
values={{
|
||||
count: restoreIndices && restoreIndices.length,
|
||||
selectOrDeselectAllLink:
|
||||
restoreIndices && restoreIndices.length > 0 ? (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
<DataStreamsAndIndicesListHelpText
|
||||
onSelectionChange={(selection) => {
|
||||
if (selection === 'all') {
|
||||
// TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed
|
||||
indicesOptions.forEach((option: EuiSelectableOption) => {
|
||||
indicesAndDataStreamsOptions.forEach((option: EuiSelectableOption) => {
|
||||
option.checked = 'on';
|
||||
});
|
||||
updateRestoreSettings({
|
||||
indices: [...snapshotIndicesAndDataStreams],
|
||||
});
|
||||
setCachedRestoreSettings({
|
||||
...cachedRestoreSettings,
|
||||
indices: [...snapshotIndicesAndDataStreams],
|
||||
});
|
||||
} else {
|
||||
// TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed
|
||||
indicesAndDataStreamsOptions.forEach((option: EuiSelectableOption) => {
|
||||
option.checked = undefined;
|
||||
});
|
||||
updateRestoreSettings({ indices: [] });
|
||||
|
@ -238,34 +309,11 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
...cachedRestoreSettings,
|
||||
indices: [],
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.deselectAllIndicesLink"
|
||||
defaultMessage="Deselect all"
|
||||
/>
|
||||
</EuiLink>
|
||||
) : (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
// TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed
|
||||
indicesOptions.forEach((option: EuiSelectableOption) => {
|
||||
option.checked = 'on';
|
||||
});
|
||||
updateRestoreSettings({ indices: [...snapshotIndices] });
|
||||
setCachedRestoreSettings({
|
||||
...cachedRestoreSettings,
|
||||
indices: [...snapshotIndices],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.selectAllIndicesLink"
|
||||
defaultMessage="Select all"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
selectedIndicesAndDataStreams={csvToArray(restoreIndices)}
|
||||
indices={snapshotIndices}
|
||||
dataStreams={snapshotDataStreams}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
@ -275,7 +323,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
{selectIndicesMode === 'list' ? (
|
||||
<EuiSelectable
|
||||
allowExclusions={false}
|
||||
options={indicesOptions}
|
||||
options={indicesAndDataStreamsOptions}
|
||||
onChange={(options) => {
|
||||
const newSelectedIndices: string[] = [];
|
||||
options.forEach(({ label, checked }) => {
|
||||
|
@ -283,7 +331,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
newSelectedIndices.push(label);
|
||||
}
|
||||
});
|
||||
setIndicesOptions(options);
|
||||
setIndicesAndDataStreamsOptions(options);
|
||||
updateRestoreSettings({ indices: [...newSelectedIndices] });
|
||||
setCachedRestoreSettings({
|
||||
...cachedRestoreSettings,
|
||||
|
@ -302,7 +350,24 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
</EuiSelectable>
|
||||
) : (
|
||||
<EuiComboBox
|
||||
options={snapshotIndices.map((index) => ({ label: index }))}
|
||||
options={comboBoxOptions}
|
||||
renderOption={({ value }) => {
|
||||
return value?.isDataStream ? (
|
||||
<EuiFlexGroup
|
||||
responsive={false}
|
||||
justifyContent="spaceBetween"
|
||||
alignItems="center"
|
||||
gutterSize="none"
|
||||
>
|
||||
<EuiFlexItem>{value.name}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<DataStreamBadge />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
value?.name
|
||||
);
|
||||
}}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.snapshotRestore.restoreForm.stepLogistics.indicesPatternPlaceholder',
|
||||
{
|
||||
|
@ -336,22 +401,22 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
|
||||
{/* Rename indices */}
|
||||
{/* Rename data streams and indices */}
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.renameIndicesTitle"
|
||||
defaultMessage="Rename indices"
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.renameDataStreamsAndIndicesTitle"
|
||||
defaultMessage="Rename data streams and indices"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.renameIndicesDescription"
|
||||
defaultMessage="Renames indices on restore."
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.renameDataStreamsAndIndicesDescription"
|
||||
defaultMessage="Renames data streams and indices on restore. Ensure that a matching index template exists for renamed data streams."
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
|
@ -361,8 +426,8 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
<EuiSwitch
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.renameIndicesLabel"
|
||||
defaultMessage="Rename indices"
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.renameDataStreamsAndIndicesLabel"
|
||||
defaultMessage="Rename data streams and indices"
|
||||
/>
|
||||
}
|
||||
checked={isRenamingIndices}
|
||||
|
@ -405,7 +470,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
>
|
||||
<EuiFieldText
|
||||
value={renamePattern}
|
||||
placeholder="index_(.+)"
|
||||
placeholder="data_(.+)"
|
||||
onChange={(e) => {
|
||||
setCachedRestoreSettings({
|
||||
...cachedRestoreSettings,
|
||||
|
@ -431,7 +496,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
>
|
||||
<EuiFieldText
|
||||
value={renameReplacement}
|
||||
placeholder="restored_index_$1"
|
||||
placeholder="restored_data_$1"
|
||||
onChange={(e) => {
|
||||
setCachedRestoreSettings({
|
||||
...cachedRestoreSettings,
|
|
@ -24,7 +24,7 @@ import {
|
|||
import { serializeRestoreSettings } from '../../../../../common/lib';
|
||||
import { useServices } from '../../../app_context';
|
||||
import { StepProps } from './';
|
||||
import { CollapsibleIndicesList } from '../../collapsible_indices_list';
|
||||
import { CollapsibleIndicesList } from '../../collapsible_lists/collapsible_indices_list';
|
||||
|
||||
export const RestoreSnapshotStepReview: React.FunctionComponent<StepProps> = ({
|
||||
restoreSettings,
|
||||
|
@ -73,8 +73,8 @@ export const RestoreSnapshotStepReview: React.FunctionComponent<StepProps> = ({
|
|||
<EuiDescriptionList textStyle="reverse">
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepReview.summaryTab.indicesLabel"
|
||||
defaultMessage="Indices"
|
||||
id="xpack.snapshotRestore.restoreForm.stepReview.summaryTab.dataStreamsAndIndicesLabel"
|
||||
defaultMessage="Data streams and indices"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
EuiSwitch,
|
||||
EuiTitle,
|
||||
EuiLink,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
import { RestoreSettings } from '../../../../../common/types';
|
||||
import { REMOVE_INDEX_SETTINGS_SUGGESTIONS } from '../../../constants';
|
||||
|
@ -28,10 +29,12 @@ import { StepProps } from './';
|
|||
export const RestoreSnapshotStepSettings: React.FunctionComponent<StepProps> = ({
|
||||
restoreSettings,
|
||||
updateRestoreSettings,
|
||||
snapshotDetails,
|
||||
errors,
|
||||
}) => {
|
||||
const { i18n } = useServices();
|
||||
const { indexSettings, ignoreIndexSettings } = restoreSettings;
|
||||
const { dataStreams } = snapshotDetails;
|
||||
|
||||
// State for index setting toggles
|
||||
const [isUsingIndexSettings, setIsUsingIndexSettings] = useState<boolean>(Boolean(indexSettings));
|
||||
|
@ -96,6 +99,23 @@ export const RestoreSnapshotStepSettings: React.FunctionComponent<StepProps> = (
|
|||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{dataStreams?.length ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut
|
||||
iconType="help"
|
||||
title={i18n.translate(
|
||||
'xpack.snapshotRestore.restoreForm.stepSettings.dataStreamsCallout.title',
|
||||
{ defaultMessage: 'Backing indices' }
|
||||
)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepSettings.dataStreamsCallout.description"
|
||||
defaultMessage="These settings also apply to the backing indices of data streams."
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</>
|
||||
) : undefined}
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
{/* Modify index settings */}
|
||||
|
|
|
@ -236,8 +236,8 @@ export const TabSummary: React.FunctionComponent<Props> = ({ policy }) => {
|
|||
<EuiFlexItem data-test-subj="indices">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.indicesLabel"
|
||||
defaultMessage="Indices"
|
||||
id="xpack.snapshotRestore.policyDetails.dataStreamsAndIndicesLabel"
|
||||
defaultMessage="Data streams and indices"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
DataPlaceholder,
|
||||
FormattedDateTime,
|
||||
CollapsibleIndicesList,
|
||||
CollapsibleDataStreamsList,
|
||||
} from '../../../../../components';
|
||||
import { linkToPolicy } from '../../../../../services/navigation';
|
||||
import { SnapshotState } from './snapshot_state';
|
||||
|
@ -40,6 +41,7 @@ export const TabSummary: React.FC<Props> = ({ snapshotDetails }) => {
|
|||
// TODO: Add a tooltip explaining that: a false value means that the cluster global state
|
||||
// is not stored as part of the snapshot.
|
||||
includeGlobalState,
|
||||
dataStreams,
|
||||
indices,
|
||||
state,
|
||||
startTimeInMillis,
|
||||
|
@ -135,6 +137,22 @@ export const TabSummary: React.FC<Props> = ({ snapshotDetails }) => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="dataStreams">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.snapshotDetails.itemDataStreamsLabel"
|
||||
defaultMessage="Data streams ({dataStreamsCount})"
|
||||
values={{ dataStreamsCount: dataStreams.length }}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
<CollapsibleDataStreamsList dataStreams={dataStreams} />
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="startTime">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
|
|
|
@ -25,13 +25,8 @@ export const PolicyAdd: React.FunctionComponent<RouteComponentProps> = ({
|
|||
const [isSaving, setIsSaving] = useState<boolean>(false);
|
||||
const [saveError, setSaveError] = useState<any>(null);
|
||||
|
||||
const {
|
||||
error: errorLoadingIndices,
|
||||
isLoading: isLoadingIndices,
|
||||
data: { indices } = {
|
||||
indices: [],
|
||||
},
|
||||
} = useLoadIndices();
|
||||
const { error: errorLoadingIndices, isLoading: isLoadingIndices, data } = useLoadIndices();
|
||||
const { indices, dataStreams } = data ?? { indices: [], dataStreams: [] };
|
||||
|
||||
// Set breadcrumb and page title
|
||||
useEffect(() => {
|
||||
|
@ -123,6 +118,7 @@ export const PolicyAdd: React.FunctionComponent<RouteComponentProps> = ({
|
|||
<PolicyForm
|
||||
policy={emptyPolicy}
|
||||
indices={indices}
|
||||
dataStreams={dataStreams}
|
||||
currentUrl={pathname}
|
||||
isSaving={isSaving}
|
||||
saveError={renderSaveError()}
|
||||
|
|
|
@ -55,9 +55,7 @@ export const PolicyEdit: React.FunctionComponent<RouteComponentProps<MatchParams
|
|||
const {
|
||||
error: errorLoadingIndices,
|
||||
isLoading: isLoadingIndices,
|
||||
data: { indices } = {
|
||||
indices: [],
|
||||
},
|
||||
data: indicesData,
|
||||
} = useLoadIndices();
|
||||
|
||||
// Load policy
|
||||
|
@ -200,7 +198,8 @@ export const PolicyEdit: React.FunctionComponent<RouteComponentProps<MatchParams
|
|||
) : null}
|
||||
<PolicyForm
|
||||
policy={policy}
|
||||
indices={indices}
|
||||
dataStreams={indicesData!.dataStreams}
|
||||
indices={indicesData!.indices}
|
||||
currentUrl={pathname}
|
||||
isEditing={true}
|
||||
isSaving={isSaving}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { API_BASE_PATH } from '../../../../common/constants';
|
||||
import { SlmPolicy, SlmPolicyPayload } from '../../../../common/types';
|
||||
import { SlmPolicy, SlmPolicyPayload, PolicyIndicesResponse } from '../../../../common/types';
|
||||
import {
|
||||
UIM_POLICY_EXECUTE,
|
||||
UIM_POLICY_DELETE,
|
||||
|
@ -40,7 +40,7 @@ export const useLoadPolicy = (name: SlmPolicy['name']) => {
|
|||
};
|
||||
|
||||
export const useLoadIndices = () => {
|
||||
return useRequest({
|
||||
return useRequest<PolicyIndicesResponse>({
|
||||
path: `${API_BASE_PATH}policies/indices`,
|
||||
method: 'get',
|
||||
});
|
||||
|
|
|
@ -18,6 +18,6 @@ export const sendRequest = (config: SendRequestConfig) => {
|
|||
return _sendRequest<any, CustomError>(httpService.httpClient, config);
|
||||
};
|
||||
|
||||
export const useRequest = (config: UseRequestConfig) => {
|
||||
return _useRequest<any, CustomError>(httpService.httpClient, config);
|
||||
export const useRequest = <D = any>(config: UseRequestConfig) => {
|
||||
return _useRequest<D, CustomError>(httpService.httpClient, config);
|
||||
};
|
||||
|
|
|
@ -48,6 +48,7 @@ export const validatePolicy = (
|
|||
snapshotName: [],
|
||||
schedule: [],
|
||||
repository: [],
|
||||
dataStreams: [],
|
||||
indices: [],
|
||||
expireAfterValue: [],
|
||||
minCount: [],
|
||||
|
@ -106,7 +107,7 @@ export const validatePolicy = (
|
|||
if (config && Array.isArray(config.indices) && config.indices.length === 0) {
|
||||
validation.errors.indices.push(
|
||||
i18n.translate('xpack.snapshotRestore.policyValidation.indicesRequiredErrorMessage', {
|
||||
defaultMessage: 'You must select at least one index.',
|
||||
defaultMessage: 'You must select at least one data stream or index.',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ export const validateRestore = (restoreSettings: RestoreSettings): RestoreValida
|
|||
if (Array.isArray(indices) && indices.length === 0) {
|
||||
validation.errors.indices.push(
|
||||
i18n.translate('xpack.snapshotRestore.restoreValidation.indicesRequiredError', {
|
||||
defaultMessage: 'You must select at least one index.',
|
||||
defaultMessage: 'You must select at least one data stream or index.',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -93,7 +93,6 @@ export const validateRestore = (restoreSettings: RestoreSettings): RestoreValida
|
|||
'xpack.snapshotRestore.restoreValidation.indexSettingsNotModifiableError',
|
||||
{
|
||||
defaultMessage: 'You can’t modify: {settings}',
|
||||
// @ts-ignore Bug filed: https://github.com/elastic/kibana/issues/39299
|
||||
values: {
|
||||
settings: unmodifiableSettings.map((setting: string, index: number) =>
|
||||
index === 0 ? `${setting} ` : setting
|
||||
|
@ -131,7 +130,6 @@ export const validateRestore = (restoreSettings: RestoreSettings): RestoreValida
|
|||
validation.errors.ignoreIndexSettings.push(
|
||||
i18n.translate('xpack.snapshotRestore.restoreValidation.indexSettingsNotRemovableError', {
|
||||
defaultMessage: 'You can’t reset: {settings}',
|
||||
// @ts-ignore Bug filed: https://github.com/elastic/kibana/issues/39299
|
||||
values: {
|
||||
settings: unremovableSettings.map((setting: string, index: number) =>
|
||||
index === 0 ? `${setting} ` : setting
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { addBasePath } from '../helpers';
|
||||
import { registerPolicyRoutes } from './policy';
|
||||
import { RouterMock, routeDependencies, RequestMock } from '../../test/helpers';
|
||||
import { ResolveIndexResponseFromES } from '../../types';
|
||||
|
||||
describe('[Snapshot and Restore API Routes] Policy', () => {
|
||||
const mockEsPolicy = {
|
||||
|
@ -324,27 +325,45 @@ describe('[Snapshot and Restore API Routes] Policy', () => {
|
|||
};
|
||||
|
||||
it('should arrify and sort index names returned from ES', async () => {
|
||||
const mockEsResponse = [
|
||||
const mockEsResponse: ResolveIndexResponseFromES = {
|
||||
indices: [
|
||||
{
|
||||
index: 'fooIndex',
|
||||
name: 'fooIndex',
|
||||
attributes: ['open'],
|
||||
},
|
||||
{
|
||||
index: 'barIndex',
|
||||
name: 'barIndex',
|
||||
attributes: ['open'],
|
||||
data_stream: 'testDataStream',
|
||||
},
|
||||
];
|
||||
],
|
||||
aliases: [],
|
||||
data_streams: [
|
||||
{
|
||||
name: 'testDataStream',
|
||||
backing_indices: ['barIndex'],
|
||||
timestamp_field: '@timestamp',
|
||||
},
|
||||
],
|
||||
};
|
||||
router.callAsCurrentUserResponses = [mockEsResponse];
|
||||
|
||||
const expectedResponse = {
|
||||
indices: ['barIndex', 'fooIndex'],
|
||||
indices: ['fooIndex'],
|
||||
dataStreams: ['testDataStream'],
|
||||
};
|
||||
await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse });
|
||||
});
|
||||
|
||||
it('should return empty array if no indices returned from ES', async () => {
|
||||
const mockEsResponse: any[] = [];
|
||||
const mockEsResponse: ResolveIndexResponseFromES = {
|
||||
indices: [],
|
||||
aliases: [],
|
||||
data_streams: [],
|
||||
};
|
||||
router.callAsCurrentUserResponses = [mockEsResponse];
|
||||
|
||||
const expectedResponse = { indices: [] };
|
||||
const expectedResponse = { indices: [], dataStreams: [] };
|
||||
await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse });
|
||||
});
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
|
||||
import { SlmPolicyEs } from '../../../common/types';
|
||||
import { SlmPolicyEs, PolicyIndicesResponse } from '../../../common/types';
|
||||
import { deserializePolicy, serializePolicy } from '../../../common/lib';
|
||||
import { getManagedPolicyNames } from '../../lib';
|
||||
import { RouteDependencies } from '../../types';
|
||||
import { RouteDependencies, ResolveIndexResponseFromES } from '../../types';
|
||||
import { addBasePath } from '../helpers';
|
||||
import { nameParameterSchema, policySchema } from './validate_schemas';
|
||||
|
||||
|
@ -232,17 +232,26 @@ export function registerPolicyRoutes({
|
|||
const { callAsCurrentUser } = ctx.snapshotRestore!.client;
|
||||
|
||||
try {
|
||||
const indices: Array<{
|
||||
index: string;
|
||||
}> = await callAsCurrentUser('cat.indices', {
|
||||
format: 'json',
|
||||
h: 'index',
|
||||
});
|
||||
const resolvedIndicesResponse: ResolveIndexResponseFromES = await callAsCurrentUser(
|
||||
'transport.request',
|
||||
{
|
||||
method: 'GET',
|
||||
path: `_resolve/index/*`,
|
||||
query: {
|
||||
expand_wildcards: 'all,hidden',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const body: PolicyIndicesResponse = {
|
||||
dataStreams: resolvedIndicesResponse.data_streams.map(({ name }) => name).sort(),
|
||||
indices: resolvedIndicesResponse.indices
|
||||
.flatMap((index) => (index.data_stream ? [] : index.name))
|
||||
.sort(),
|
||||
};
|
||||
|
||||
return res.ok({
|
||||
body: {
|
||||
indices: indices.map(({ index }) => index).sort(),
|
||||
},
|
||||
body,
|
||||
});
|
||||
} catch (e) {
|
||||
if (isEsError(e)) {
|
||||
|
|
|
@ -15,6 +15,7 @@ const defaultSnapshot = {
|
|||
versionId: undefined,
|
||||
version: undefined,
|
||||
indices: [],
|
||||
dataStreams: [],
|
||||
includeGlobalState: undefined,
|
||||
state: undefined,
|
||||
startTime: undefined,
|
||||
|
|
|
@ -31,4 +31,20 @@ export interface RouteDependencies {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* An object representing a resolved index, data stream or alias
|
||||
*/
|
||||
interface IndexAndAliasFromEs {
|
||||
name: string;
|
||||
// per https://github.com/elastic/elasticsearch/pull/57626
|
||||
attributes: Array<'open' | 'closed' | 'hidden' | 'frozen'>;
|
||||
data_stream?: string;
|
||||
}
|
||||
|
||||
export interface ResolveIndexResponseFromES {
|
||||
indices: IndexAndAliasFromEs[];
|
||||
aliases: IndexAndAliasFromEs[];
|
||||
data_streams: Array<{ name: string; backing_indices: string[]; timestamp_field: string }>;
|
||||
}
|
||||
|
||||
export type CallAsCurrentUser = LegacyScopedClusterClient['callAsCurrentUser'];
|
||||
|
|
|
@ -13,13 +13,23 @@ export const getSnapshot = ({
|
|||
state = 'SUCCESS',
|
||||
indexFailures = [],
|
||||
totalIndices = getRandomNumber(),
|
||||
} = {}) => ({
|
||||
totalDataStreams = getRandomNumber(),
|
||||
}: Partial<{
|
||||
repository: string;
|
||||
snapshot: string;
|
||||
uuid: string;
|
||||
state: string;
|
||||
indexFailures: any[];
|
||||
totalIndices: number;
|
||||
totalDataStreams: number;
|
||||
}> = {}) => ({
|
||||
repository,
|
||||
snapshot,
|
||||
uuid,
|
||||
versionId: 8000099,
|
||||
version: '8.0.0',
|
||||
indices: new Array(totalIndices).fill('').map(getRandomString),
|
||||
dataStreams: new Array(totalDataStreams).fill('').map(getRandomString),
|
||||
includeGlobalState: 1,
|
||||
state,
|
||||
startTime: '2019-05-23T06:25:15.896Z',
|
||||
|
|
|
@ -13387,7 +13387,6 @@
|
|||
"xpack.snapshotRestore.policyDetails.includeGlobalStateFalseLabel": "いいえ",
|
||||
"xpack.snapshotRestore.policyDetails.includeGlobalStateLabel": "グローバルステータスを含める",
|
||||
"xpack.snapshotRestore.policyDetails.includeGlobalStateTrueLabel": "はい",
|
||||
"xpack.snapshotRestore.policyDetails.indicesLabel": "インデックス",
|
||||
"xpack.snapshotRestore.policyDetails.inProgressSnapshotLinkText": "「{snapshotName}」が進行中",
|
||||
"xpack.snapshotRestore.policyDetails.lastFailure.dateLabel": "日付",
|
||||
"xpack.snapshotRestore.policyDetails.lastFailure.detailsAriaLabel": "ポリシー「{name}」の前回のエラーの詳細",
|
||||
|
@ -13495,10 +13494,8 @@
|
|||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.includeGlobalStateFalseLabel": "いいえ",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.includeGlobalStateLabel": "グローバルステータスを含める",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.includeGlobalStateTrueLabel": "はい",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.indicesLabel": "インデックス",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.nameLabel": "ポリシー名",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.partialFalseLabel": "いいえ",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.partialLabel": "部分シャードを許可",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.partialTrueLabel": "はい",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.repositoryLabel": "レポジトリ",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.scheduleLabel": "スケジュール",
|
||||
|
@ -13507,7 +13504,6 @@
|
|||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.snapshotNameLabel": "スナップショット名",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTabTitle": "まとめ",
|
||||
"xpack.snapshotRestore.policyForm.stepReviewTitle": "レビューポリシー",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.allIndicesLabel": "システムインデックスを含むすべてのインデックス",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.deselectAllIndicesLink": "すべて選択解除",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.docsButtonLabel": "スナップショット設定ドキュメント",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.ignoreUnavailableDescription": "スナップショットの撮影時に利用不可能なインデックスを無視します。これが設定されていない場合、スナップショット全体がエラーになります。",
|
||||
|
@ -13515,19 +13511,15 @@
|
|||
"xpack.snapshotRestore.policyForm.stepSettings.ignoreUnavailableLabel": "利用不可能なインデックスを無視",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.includeGlobalStateDescription": "スナップショットの一部としてクラスターのグローバルステータスを格納します。",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.includeGlobalStateDescriptionTitle": "グローバルステータスを含める",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesDescription": "バックアップするインデックスです。",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesPatternLabel": "インデックスパターン",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesPatternPlaceholder": "logstash-* などのインデックスパターンを入力",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesTitle": "インデックス",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesToggleCustomLink": "インデックスパターンを使用",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesToggleListLink": "インデックスを選択",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesTooltip": "クラウドで管理されたポリシーにはすべてのインデックスが必要です。",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.partialDescription": "利用不可能なプライマリシャードのインデックスのスナップショットを許可します。これが設定されていない場合、スナップショット全体がエラーになります。",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.partialDescriptionTitle": "部分インデックスを許可",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.partialIndicesToggleSwitch": "部分インデックスを許可",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.policyIncludeGlobalStateLabel": "グローバルステータスを含める",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.selectAllIndicesLink": "すべて選択",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.selectIndicesHelpText": "{count} 件の{count, plural, one {インデックス} other {インデックス}}がバックアップされます。{selectOrDeselectAllLink}",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.selectIndicesLabel": "インデックスを選択",
|
||||
"xpack.snapshotRestore.policyForm.stepSettingsTitle": "スナップショット設定",
|
||||
"xpack.snapshotRestore.policyList.deniedPrivilegeDescription": "スナップショットライフサイクルポリシーを管理するには、{privilegesCount, plural, one {このクラスター特権} other {これらのクラスター特権}}が必要です: {missingPrivileges}。",
|
||||
|
@ -13874,31 +13866,22 @@
|
|||
"xpack.snapshotRestore.restoreForm.navigation.stepSettingsName": "インデックス設定",
|
||||
"xpack.snapshotRestore.restoreForm.nextButtonLabel": "次へ",
|
||||
"xpack.snapshotRestore.restoreForm.savingButtonLabel": "復元中...",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.allIndicesLabel": "システムインデックスを含むすべてのインデックス",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.deselectAllIndicesLink": "すべて選択解除",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.docsButtonLabel": "スナップショットと復元ドキュメント",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.includeGlobalStateDescription": "現在クラスターに存在しないテンプレートを復元し、テンプレートを同じ名前で上書きします。永続的な設定も復元します。",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.includeGlobalStateDisabledDescription": "このスナップショットでは使用できません。",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.includeGlobalStateLabel": "グローバル状態の復元",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.includeGlobalStateTitle": "グローバル状態の復元",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.indicesDescription": "存在しない場合は、新しいインデックスを作成します。閉じていて、スナップショットインデックスと同じ数のシャードがある場合は、既存のインデックスを復元します。",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.indicesPatternLabel": "インデックスパターン",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.indicesPatternPlaceholder": "logstash-* などのインデックスパターンを入力",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.indicesTitle": "インデックス",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.indicesToggleCustomLink": "インデックスパターンを使用",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.indicesToggleListLink": "インデックスを選択",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.partialDescription": "すべてのシャードのスナップショットがないインデックスを復元できます。",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.partialLabel": "部分復元",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.partialTitle": "部分復元",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.renameIndicesDescription": "復元時にインデックス名を変更します。",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.renameIndicesLabel": "インデックス名の変更",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.renameIndicesTitle": "インデックス名の変更",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.renamePatternHelpText": "正規表現を使用",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.renamePatternLabel": "取り込みパターン",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.renameReplacementLabel": "置換パターン",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.selectAllIndicesLink": "すべて選択",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.selectIndicesHelpText": "{count} 件の{count, plural, one {インデックス} other {インデックス}}が復元されます。{selectOrDeselectAllLink}",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.selectIndicesLabel": "インデックスを選択",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogisticsTitle": "詳細を復元",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.jsonTab.jsonAriaLabel": "実行する設定を復元",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.jsonTabTitle": "JSON",
|
||||
|
@ -13908,7 +13891,6 @@
|
|||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.includeGlobalStateLabel": "グローバル状態の復元",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.includeGlobalStateTrueValue": "はい",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.indexSettingsLabel": "修正",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.indicesLabel": "インデックス",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.noSettingsValue": "インデックス設定の修正はありません",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.partialFalseValue": "いいえ",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.partialLabel": "部分復元",
|
||||
|
|
|
@ -13392,7 +13392,6 @@
|
|||
"xpack.snapshotRestore.policyDetails.includeGlobalStateFalseLabel": "否",
|
||||
"xpack.snapshotRestore.policyDetails.includeGlobalStateLabel": "包括全局状态",
|
||||
"xpack.snapshotRestore.policyDetails.includeGlobalStateTrueLabel": "是",
|
||||
"xpack.snapshotRestore.policyDetails.indicesLabel": "索引",
|
||||
"xpack.snapshotRestore.policyDetails.inProgressSnapshotLinkText": "“{snapshotName}”正在进行中",
|
||||
"xpack.snapshotRestore.policyDetails.lastFailure.dateLabel": "日期",
|
||||
"xpack.snapshotRestore.policyDetails.lastFailure.detailsAriaLabel": "策略“{name}”的上次失败详情",
|
||||
|
@ -13500,10 +13499,8 @@
|
|||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.includeGlobalStateFalseLabel": "否",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.includeGlobalStateLabel": "包括全局状态",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.includeGlobalStateTrueLabel": "是",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.indicesLabel": "索引",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.nameLabel": "策略名称",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.partialFalseLabel": "否",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.partialLabel": "允许部分分片",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.partialTrueLabel": "是",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.repositoryLabel": "存储库",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.scheduleLabel": "计划",
|
||||
|
@ -13512,7 +13509,6 @@
|
|||
"xpack.snapshotRestore.policyForm.stepReview.summaryTab.snapshotNameLabel": "快照名称",
|
||||
"xpack.snapshotRestore.policyForm.stepReview.summaryTabTitle": "总结",
|
||||
"xpack.snapshotRestore.policyForm.stepReviewTitle": "复查策略",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.allIndicesLabel": "所有索引,包括系统索引",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.deselectAllIndicesLink": "取消全选",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.docsButtonLabel": "快照设置文档",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.ignoreUnavailableDescription": "拍取快照时忽略不可用的索引。否则,整个快照将失败。",
|
||||
|
@ -13520,19 +13516,15 @@
|
|||
"xpack.snapshotRestore.policyForm.stepSettings.ignoreUnavailableLabel": "忽略不可用索引",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.includeGlobalStateDescription": "将集群的全局状态存储为快照的一部分。",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.includeGlobalStateDescriptionTitle": "包括全局状态",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesDescription": "要备份的索引。",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesPatternLabel": "索引模式",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesPatternPlaceholder": "输入索引模式,例如 logstash-*",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesTitle": "索引",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesToggleCustomLink": "使用索引模式",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesToggleListLink": "选择索引",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.indicesTooltip": "云托管的策略需要所有索引。",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.partialDescription": "允许具有不可用主分片的索引的快照。否则,整个快照将失败。",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.partialDescriptionTitle": "允许部分索引",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.partialIndicesToggleSwitch": "允许部分索引",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.policyIncludeGlobalStateLabel": "包括全局状态",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.selectAllIndicesLink": "全选",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.selectIndicesHelpText": "将备份 {count} 个 {count, plural, one {索引} other {索引}}。{selectOrDeselectAllLink}",
|
||||
"xpack.snapshotRestore.policyForm.stepSettings.selectIndicesLabel": "选择索引",
|
||||
"xpack.snapshotRestore.policyForm.stepSettingsTitle": "快照设置",
|
||||
"xpack.snapshotRestore.policyList.deniedPrivilegeDescription": "要管理快照生命周期策略,必须具有{privilegesCount, plural, one {以下集群权限} other {以下集群权限}}:{missingPrivileges}。",
|
||||
|
@ -13879,31 +13871,22 @@
|
|||
"xpack.snapshotRestore.restoreForm.navigation.stepSettingsName": "索引设置",
|
||||
"xpack.snapshotRestore.restoreForm.nextButtonLabel": "下一步",
|
||||
"xpack.snapshotRestore.restoreForm.savingButtonLabel": "正在还原……",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.allIndicesLabel": "所有索引,包括系统索引",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.deselectAllIndicesLink": "取消全选",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.docsButtonLabel": "快照和还原文档",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.includeGlobalStateDescription": "还原当前在集群中不存在的模板并覆盖同名模板。同时还原永久性设置。",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.includeGlobalStateDisabledDescription": "不适用于此快照。",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.includeGlobalStateLabel": "还原全局状态",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.includeGlobalStateTitle": "还原全局状态",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.indicesDescription": "如果不存在,则创建新索引。如果现有索引已关闭且与快照索引有相同数目的分片,则还原现有索引。",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.indicesPatternLabel": "索引模式",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.indicesPatternPlaceholder": "输入索引模式,例如 logstash-*",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.indicesTitle": "索引",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.indicesToggleCustomLink": "使用索引模式",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.indicesToggleListLink": "选择索引",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.partialDescription": "允许还原不具有所有分片的快照的索引。",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.partialLabel": "部分还原",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.partialTitle": "部分还原",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.renameIndicesDescription": "还原时重命名索引。",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.renameIndicesLabel": "重命名索引",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.renameIndicesTitle": "重命名索引",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.renamePatternHelpText": "使用正则表达式",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.renamePatternLabel": "捕获模式",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.renameReplacementLabel": "替换模式",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.selectAllIndicesLink": "全选",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.selectIndicesHelpText": "将还原 {count} 个 {count, plural, one {索引} other {索引}}。{selectOrDeselectAllLink}",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogistics.selectIndicesLabel": "选择索引",
|
||||
"xpack.snapshotRestore.restoreForm.stepLogisticsTitle": "还原详情",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.jsonTab.jsonAriaLabel": "还原要执行的设置",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.jsonTabTitle": "JSON",
|
||||
|
@ -13913,7 +13896,6 @@
|
|||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.includeGlobalStateLabel": "还原全局状态",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.includeGlobalStateTrueValue": "鏄",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.indexSettingsLabel": "修改",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.indicesLabel": "索引",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.noSettingsValue": "无索引设置修改",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.partialFalseValue": "否",
|
||||
"xpack.snapshotRestore.restoreForm.stepReview.summaryTab.partialLabel": "部分还原",
|
||||
|
|
Loading…
Reference in a new issue