mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* fix URI decoding and editing of a policy which backs up all indices * fix type issue * fix general use of encoding and update decode algo * fix serialisation of snapshots and added a test * Fix test description name * Update attempt_to_uri_decode.ts * catch errors from decoding in the already throwing code Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
db735b3433
commit
f583c4e734
24 changed files with 277 additions and 163 deletions
|
@ -4,55 +4,17 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { deserializeSnapshotDetails } from './snapshot_serialization';
|
||||
import { deserializeSnapshotDetails, serializeSnapshotConfig } from './snapshot_serialization';
|
||||
|
||||
describe('deserializeSnapshotDetails', () => {
|
||||
test('deserializes a snapshot', () => {
|
||||
expect(
|
||||
deserializeSnapshotDetails(
|
||||
'repositoryName',
|
||||
{
|
||||
snapshot: 'snapshot name',
|
||||
uuid: 'UUID',
|
||||
version_id: 5,
|
||||
version: 'version',
|
||||
indices: ['index2', 'index3', 'index1'],
|
||||
include_global_state: false,
|
||||
state: 'SUCCESS',
|
||||
start_time: '0',
|
||||
start_time_in_millis: 0,
|
||||
end_time: '1',
|
||||
end_time_in_millis: 1,
|
||||
duration_in_millis: 1,
|
||||
shards: {
|
||||
total: 3,
|
||||
failed: 1,
|
||||
successful: 2,
|
||||
},
|
||||
failures: [
|
||||
{
|
||||
index: 'z',
|
||||
shard: 1,
|
||||
},
|
||||
{
|
||||
index: 'a',
|
||||
shard: 3,
|
||||
},
|
||||
{
|
||||
index: 'a',
|
||||
shard: 1,
|
||||
},
|
||||
{
|
||||
index: 'a',
|
||||
shard: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
'found-snapshots',
|
||||
[
|
||||
describe('Snapshot serialization and deserialization', () => {
|
||||
describe('deserializeSnapshotDetails', () => {
|
||||
test('deserializes a snapshot', () => {
|
||||
expect(
|
||||
deserializeSnapshotDetails(
|
||||
'repositoryName',
|
||||
{
|
||||
snapshot: 'last_successful_snapshot',
|
||||
uuid: 'last_successful_snapshot_UUID',
|
||||
snapshot: 'snapshot name',
|
||||
uuid: 'UUID',
|
||||
version_id: 5,
|
||||
version: 'version',
|
||||
indices: ['index2', 'index3', 'index1'],
|
||||
|
@ -87,56 +49,109 @@ describe('deserializeSnapshotDetails', () => {
|
|||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
)
|
||||
).toEqual({
|
||||
repository: 'repositoryName',
|
||||
snapshot: 'snapshot name',
|
||||
uuid: 'UUID',
|
||||
versionId: 5,
|
||||
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: [
|
||||
{
|
||||
index: 'a',
|
||||
failures: [
|
||||
'found-snapshots',
|
||||
[
|
||||
{
|
||||
shard: 1,
|
||||
snapshot: 'last_successful_snapshot',
|
||||
uuid: 'last_successful_snapshot_UUID',
|
||||
version_id: 5,
|
||||
version: 'version',
|
||||
indices: ['index2', 'index3', 'index1'],
|
||||
include_global_state: false,
|
||||
state: 'SUCCESS',
|
||||
start_time: '0',
|
||||
start_time_in_millis: 0,
|
||||
end_time: '1',
|
||||
end_time_in_millis: 1,
|
||||
duration_in_millis: 1,
|
||||
shards: {
|
||||
total: 3,
|
||||
failed: 1,
|
||||
successful: 2,
|
||||
},
|
||||
failures: [
|
||||
{
|
||||
index: 'z',
|
||||
shard: 1,
|
||||
},
|
||||
{
|
||||
index: 'a',
|
||||
shard: 3,
|
||||
},
|
||||
{
|
||||
index: 'a',
|
||||
shard: 1,
|
||||
},
|
||||
{
|
||||
index: 'a',
|
||||
shard: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
shard: 2,
|
||||
},
|
||||
{
|
||||
shard: 3,
|
||||
},
|
||||
],
|
||||
]
|
||||
)
|
||||
).toEqual({
|
||||
repository: 'repositoryName',
|
||||
snapshot: 'snapshot name',
|
||||
uuid: 'UUID',
|
||||
versionId: 5,
|
||||
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: [
|
||||
{
|
||||
index: 'a',
|
||||
failures: [
|
||||
{
|
||||
shard: 1,
|
||||
},
|
||||
{
|
||||
shard: 2,
|
||||
},
|
||||
{
|
||||
shard: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
index: 'z',
|
||||
failures: [
|
||||
{
|
||||
shard: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
state: 'SUCCESS',
|
||||
startTime: '0',
|
||||
startTimeInMillis: 0,
|
||||
endTime: '1',
|
||||
endTimeInMillis: 1,
|
||||
durationInMillis: 1,
|
||||
shards: {
|
||||
total: 3,
|
||||
failed: 1,
|
||||
successful: 2,
|
||||
},
|
||||
{
|
||||
index: 'z',
|
||||
failures: [
|
||||
{
|
||||
shard: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
state: 'SUCCESS',
|
||||
startTime: '0',
|
||||
startTimeInMillis: 0,
|
||||
endTime: '1',
|
||||
endTimeInMillis: 1,
|
||||
durationInMillis: 1,
|
||||
shards: {
|
||||
total: 3,
|
||||
failed: 1,
|
||||
successful: 2,
|
||||
},
|
||||
managedRepository: 'found-snapshots',
|
||||
isLastSuccessfulSnapshot: false,
|
||||
managedRepository: 'found-snapshots',
|
||||
isLastSuccessfulSnapshot: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('serializeSnapshotConfig', () => {
|
||||
test('serializes config as expected', () => {
|
||||
const metadata = { test: 'what have you' };
|
||||
expect(serializeSnapshotConfig({ metadata, indices: '.k*' })).toEqual({
|
||||
metadata,
|
||||
indices: ['.k*'],
|
||||
});
|
||||
});
|
||||
test('serializes empty config as expected', () => {
|
||||
expect(serializeSnapshotConfig({})).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -131,10 +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 maybeIndicesArray = csvToArray(indices);
|
||||
|
||||
const snapshotConfigEs: SnapshotConfigEs = {
|
||||
indices: indicesArray,
|
||||
indices: maybeIndicesArray,
|
||||
ignore_unavailable: ignoreUnavailable,
|
||||
include_global_state: includeGlobalState,
|
||||
partial,
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const csvToArray = (indices?: string | string[]): string[] => {
|
||||
export const csvToArray = (indices?: string | string[]): string[] | undefined => {
|
||||
return indices && Array.isArray(indices)
|
||||
? indices
|
||||
: typeof indices === 'string'
|
||||
? indices.split(',')
|
||||
: [];
|
||||
: undefined;
|
||||
};
|
||||
|
|
|
@ -24,7 +24,7 @@ const maximumItemPreviewCount = 10;
|
|||
|
||||
export const useCollapsibleList = ({ items }: Arg): ReturnValue => {
|
||||
const [isShowingFullList, setIsShowingFullList] = useState<boolean>(false);
|
||||
const itemsArray = csvToArray(items);
|
||||
const itemsArray = csvToArray(items) ?? [];
|
||||
const displayItems: ChildItems =
|
||||
items === undefined
|
||||
? 'all'
|
||||
|
|
|
@ -35,11 +35,17 @@ export const PolicyStepRetention: React.FunctionComponent<StepProps> = ({
|
|||
}) => {
|
||||
const { retention = {} } = policy;
|
||||
|
||||
const updatePolicyRetention = (updatedFields: Partial<SlmPolicyPayload['retention']>): void => {
|
||||
const updatePolicyRetention = (
|
||||
updatedFields: Partial<SlmPolicyPayload['retention']>,
|
||||
validationHelperData = {}
|
||||
): void => {
|
||||
const newRetention = { ...retention, ...updatedFields };
|
||||
updatePolicy({
|
||||
retention: newRetention,
|
||||
});
|
||||
updatePolicy(
|
||||
{
|
||||
retention: newRetention,
|
||||
},
|
||||
validationHelperData
|
||||
);
|
||||
};
|
||||
|
||||
// State for touched inputs
|
||||
|
|
|
@ -25,7 +25,7 @@ import {
|
|||
|
||||
import { SlmPolicyPayload } from '../../../../../../../../common/types';
|
||||
import { useServices } from '../../../../../../app_context';
|
||||
import { PolicyValidation } from '../../../../../../services/validation';
|
||||
import { PolicyValidation, ValidatePolicyHelperData } from '../../../../../../services/validation';
|
||||
|
||||
import { orderDataStreamsAndIndices } from '../../../../../lib';
|
||||
import { DataStreamBadge } from '../../../../../data_stream_badge';
|
||||
|
@ -34,12 +34,16 @@ import { mapSelectionToIndicesOptions, determineListMode } from './helpers';
|
|||
|
||||
import { DataStreamsAndIndicesListHelpText } from './data_streams_and_indices_list_help_text';
|
||||
|
||||
interface IndicesConfig {
|
||||
indices?: string[] | string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
isManagedPolicy: boolean;
|
||||
policy: SlmPolicyPayload;
|
||||
indices: string[];
|
||||
dataStreams: string[];
|
||||
onUpdate: (arg: { indices?: string[] | string }) => void;
|
||||
onUpdate: (arg: IndicesConfig, validateHelperData: ValidatePolicyHelperData) => void;
|
||||
errors: PolicyValidation['errors'];
|
||||
}
|
||||
|
||||
|
@ -53,7 +57,7 @@ export const IndicesAndDataStreamsField: FunctionComponent<Props> = ({
|
|||
dataStreams,
|
||||
indices,
|
||||
policy,
|
||||
onUpdate,
|
||||
onUpdate: _onUpdate,
|
||||
errors,
|
||||
}) => {
|
||||
const { i18n } = useServices();
|
||||
|
@ -66,6 +70,12 @@ export const IndicesAndDataStreamsField: FunctionComponent<Props> = ({
|
|||
!config.indices || (Array.isArray(config.indices) && config.indices.length === 0)
|
||||
);
|
||||
|
||||
const onUpdate = (data: IndicesConfig) => {
|
||||
_onUpdate(data, {
|
||||
validateIndicesCount: !isAllIndices,
|
||||
});
|
||||
};
|
||||
|
||||
const [indicesAndDataStreamsSelection, setIndicesAndDataStreamsSelection] = useState<string[]>(
|
||||
() =>
|
||||
Array.isArray(config.indices) && !isAllIndices
|
||||
|
|
|
@ -31,11 +31,17 @@ export const PolicyStepSettings: React.FunctionComponent<StepProps> = ({
|
|||
}) => {
|
||||
const { config = {}, isManagedPolicy } = policy;
|
||||
|
||||
const updatePolicyConfig = (updatedFields: Partial<SlmPolicyPayload['config']>): void => {
|
||||
const updatePolicyConfig = (
|
||||
updatedFields: Partial<SlmPolicyPayload['config']>,
|
||||
validationHelperData = {}
|
||||
): void => {
|
||||
const newConfig = { ...config, ...updatedFields };
|
||||
updatePolicy({
|
||||
config: newConfig,
|
||||
});
|
||||
updatePolicy(
|
||||
{
|
||||
config: newConfig,
|
||||
},
|
||||
validationHelperData
|
||||
);
|
||||
};
|
||||
|
||||
const renderIgnoreUnavailableField = () => (
|
||||
|
|
|
@ -311,7 +311,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
});
|
||||
}
|
||||
}}
|
||||
selectedIndicesAndDataStreams={csvToArray(restoreIndices)}
|
||||
selectedIndicesAndDataStreams={csvToArray(restoreIndices) ?? []}
|
||||
indices={snapshotIndices}
|
||||
dataStreams={snapshotDataStreams}
|
||||
/>
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
||||
export const attemptToURIDecode = (value: string) => {
|
||||
let result: string;
|
||||
|
||||
try {
|
||||
result = decodeURI(value);
|
||||
result = decodeURIComponent(result);
|
||||
} catch (e1) {
|
||||
try {
|
||||
result = decodeURIComponent(value);
|
||||
} catch (e2) {
|
||||
result = value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
|
@ -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 { useDecodedParams } from './use_decoded_params';
|
|
@ -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.
|
||||
*/
|
||||
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { attemptToURIDecode } from './attempt_to_uri_decode';
|
||||
|
||||
export const useDecodedParams = <
|
||||
Params extends { [K in keyof Params]?: string } = {}
|
||||
>(): Params => {
|
||||
const params = useParams<Record<string, string>>();
|
||||
const decodedParams = {} as Params;
|
||||
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value) {
|
||||
(decodedParams as any)[key] = attemptToURIDecode(value);
|
||||
}
|
||||
}
|
||||
|
||||
return decodedParams;
|
||||
};
|
|
@ -89,7 +89,7 @@ export const SnapshotRestoreHome: React.FunctionComponent<RouteComponentProps<Ma
|
|||
}
|
||||
|
||||
const onSectionChange = (newSection: Section) => {
|
||||
history.push(`${BASE_PATH}/${newSection}`);
|
||||
history.push(encodeURI(`${BASE_PATH}/${encodeURIComponent(newSection)}`));
|
||||
};
|
||||
|
||||
// Set breadcrumb and page title
|
||||
|
|
|
@ -24,6 +24,8 @@ import {
|
|||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
import { SlmPolicy } from '../../../../../../common/types';
|
||||
import { useServices } from '../../../../app_context';
|
||||
import { SectionError, Error } from '../../../../../shared_imports';
|
||||
|
@ -41,8 +43,6 @@ import {
|
|||
} from '../../../../components';
|
||||
import { TabSummary, TabHistory } from './tabs';
|
||||
|
||||
import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
interface Props {
|
||||
policyName: SlmPolicy['name'];
|
||||
onClose: () => void;
|
||||
|
|
|
@ -9,6 +9,8 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { EuiEmptyPrompt, EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
import {
|
||||
SectionError,
|
||||
Error,
|
||||
|
@ -20,6 +22,7 @@ import { SlmPolicy } from '../../../../../common/types';
|
|||
import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../common';
|
||||
import { SectionLoading } from '../../../components';
|
||||
import { BASE_PATH, UIM_POLICY_LIST_LOAD } from '../../../constants';
|
||||
import { useDecodedParams } from '../../../lib';
|
||||
import { useLoadPolicies, useLoadRetentionSettings } from '../../../services/http';
|
||||
import { linkToAddPolicy, linkToPolicy } from '../../../services/navigation';
|
||||
import { useServices } from '../../../app_context';
|
||||
|
@ -28,18 +31,14 @@ import { PolicyDetails } from './policy_details';
|
|||
import { PolicyTable } from './policy_table';
|
||||
import { PolicyRetentionSchedule } from './policy_retention_schedule';
|
||||
|
||||
import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
interface MatchParams {
|
||||
policyName?: SlmPolicy['name'];
|
||||
}
|
||||
|
||||
export const PolicyList: React.FunctionComponent<RouteComponentProps<MatchParams>> = ({
|
||||
match: {
|
||||
params: { policyName },
|
||||
},
|
||||
history,
|
||||
}) => {
|
||||
const { policyName } = useDecodedParams<MatchParams>();
|
||||
const {
|
||||
error,
|
||||
isLoading,
|
||||
|
|
|
@ -7,11 +7,14 @@
|
|||
import React, { Fragment, useEffect } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
|
||||
|
||||
import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
import { Repository } from '../../../../../common/types';
|
||||
import { SectionError, Error } from '../../../../shared_imports';
|
||||
import { SectionLoading } from '../../../components';
|
||||
import { useDecodedParams } from '../../../lib';
|
||||
import { BASE_PATH, UIM_REPOSITORY_LIST_LOAD } from '../../../constants';
|
||||
import { useServices } from '../../../app_context';
|
||||
import { useLoadRepositories } from '../../../services/http';
|
||||
|
@ -20,18 +23,14 @@ import { linkToAddRepository, linkToRepository } from '../../../services/navigat
|
|||
import { RepositoryDetails } from './repository_details';
|
||||
import { RepositoryTable } from './repository_table';
|
||||
|
||||
import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
interface MatchParams {
|
||||
repositoryName?: Repository['name'];
|
||||
}
|
||||
|
||||
export const RepositoryList: React.FunctionComponent<RouteComponentProps<MatchParams>> = ({
|
||||
match: {
|
||||
params: { repositoryName },
|
||||
},
|
||||
history,
|
||||
}) => {
|
||||
const { repositoryName } = useDecodedParams<MatchParams>();
|
||||
const {
|
||||
error,
|
||||
isLoading,
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
linkToSnapshot,
|
||||
} from '../../../services/navigation';
|
||||
import { useServices } from '../../../app_context';
|
||||
import { useDecodedParams } from '../../../lib';
|
||||
import { SnapshotDetails } from './snapshot_details';
|
||||
import { SnapshotTable } from './snapshot_table';
|
||||
|
||||
|
@ -35,12 +36,10 @@ interface MatchParams {
|
|||
}
|
||||
|
||||
export const SnapshotList: React.FunctionComponent<RouteComponentProps<MatchParams>> = ({
|
||||
match: {
|
||||
params: { repositoryName, snapshotId },
|
||||
},
|
||||
location: { search },
|
||||
history,
|
||||
}) => {
|
||||
const { repositoryName, snapshotId } = useDecodedParams<MatchParams>();
|
||||
const {
|
||||
error,
|
||||
isLoading,
|
||||
|
|
|
@ -43,7 +43,7 @@ export const PolicyAdd: React.FunctionComponent<RouteComponentProps> = ({
|
|||
if (error) {
|
||||
setSaveError(error);
|
||||
} else {
|
||||
history.push(`${BASE_PATH}/policies/${name}`);
|
||||
history.push(encodeURI(`${BASE_PATH}/policies/${encodeURIComponent(name)}`));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import { RouteComponentProps } from 'react-router-dom';
|
|||
import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle, EuiCallOut } from '@elastic/eui';
|
||||
import { SlmPolicyPayload } from '../../../../common/types';
|
||||
import { SectionError, Error } from '../../../shared_imports';
|
||||
import { useDecodedParams } from '../../lib';
|
||||
import { TIME_UNITS } from '../../../../common/constants';
|
||||
import { SectionLoading, PolicyForm } from '../../components';
|
||||
import { BASE_PATH } from '../../constants';
|
||||
|
@ -22,12 +23,10 @@ interface MatchParams {
|
|||
}
|
||||
|
||||
export const PolicyEdit: React.FunctionComponent<RouteComponentProps<MatchParams>> = ({
|
||||
match: {
|
||||
params: { name },
|
||||
},
|
||||
history,
|
||||
location: { pathname },
|
||||
}) => {
|
||||
const { name } = useDecodedParams<MatchParams>();
|
||||
const { i18n } = useServices();
|
||||
|
||||
// Set breadcrumb and page title
|
||||
|
@ -83,12 +82,12 @@ export const PolicyEdit: React.FunctionComponent<RouteComponentProps<MatchParams
|
|||
if (error) {
|
||||
setSaveError(error);
|
||||
} else {
|
||||
history.push(`${BASE_PATH}/policies/${name}`);
|
||||
history.push(encodeURI(`${BASE_PATH}/policies/${encodeURIComponent(name)}`));
|
||||
}
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
history.push(`${BASE_PATH}/policies/${name}`);
|
||||
history.push(encodeURI(`${BASE_PATH}/policies/${encodeURIComponent(name)}`));
|
||||
};
|
||||
|
||||
const renderLoading = () => {
|
||||
|
|
|
@ -44,7 +44,11 @@ export const RepositoryAdd: React.FunctionComponent<RouteComponentProps> = ({
|
|||
} else {
|
||||
const { redirect } = parse(search.replace(/^\?/, ''), { sort: false });
|
||||
|
||||
history.push(redirect ? (redirect as string) : `${BASE_PATH}/${section}/${name}`);
|
||||
history.push(
|
||||
redirect
|
||||
? (redirect as string)
|
||||
: encodeURI(`${BASE_PATH}/${encodeURIComponent(section)}/${encodeURIComponent(name)}`)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -16,18 +16,17 @@ import { BASE_PATH, Section } from '../../constants';
|
|||
import { useServices } from '../../app_context';
|
||||
import { breadcrumbService, docTitleService } from '../../services/navigation';
|
||||
import { editRepository, useLoadRepository } from '../../services/http';
|
||||
import { useDecodedParams } from '../../lib';
|
||||
|
||||
interface MatchParams {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const RepositoryEdit: React.FunctionComponent<RouteComponentProps<MatchParams>> = ({
|
||||
match: {
|
||||
params: { name },
|
||||
},
|
||||
history,
|
||||
}) => {
|
||||
const { i18n } = useServices();
|
||||
const { name } = useDecodedParams<MatchParams>();
|
||||
const section = 'repositories' as Section;
|
||||
|
||||
// Set breadcrumb and page title
|
||||
|
@ -70,7 +69,9 @@ export const RepositoryEdit: React.FunctionComponent<RouteComponentProps<MatchPa
|
|||
if (error) {
|
||||
setSaveError(error);
|
||||
} else {
|
||||
history.push(`${BASE_PATH}/${section}/${name}`);
|
||||
history.push(
|
||||
encodeURI(`${BASE_PATH}/${encodeURIComponent(section)}/${encodeURIComponent(name)}`)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import { SectionLoading, RestoreSnapshotForm } from '../../components';
|
|||
import { useServices } from '../../app_context';
|
||||
import { breadcrumbService, docTitleService } from '../../services/navigation';
|
||||
import { useLoadSnapshot, executeRestore } from '../../services/http';
|
||||
import { useDecodedParams } from '../../lib';
|
||||
|
||||
interface MatchParams {
|
||||
repositoryName: string;
|
||||
|
@ -22,12 +23,10 @@ interface MatchParams {
|
|||
}
|
||||
|
||||
export const RestoreSnapshot: React.FunctionComponent<RouteComponentProps<MatchParams>> = ({
|
||||
match: {
|
||||
params: { repositoryName, snapshotId },
|
||||
},
|
||||
history,
|
||||
}) => {
|
||||
const { i18n } = useServices();
|
||||
const { repositoryName, snapshotId } = useDecodedParams<MatchParams>();
|
||||
|
||||
// Set breadcrumb and page title
|
||||
useEffect(() => {
|
||||
|
|
|
@ -13,33 +13,37 @@ export function linkToRepositories() {
|
|||
}
|
||||
|
||||
export function linkToRepository(repositoryName: string) {
|
||||
return `/repositories/${encodeURIComponent(repositoryName)}`;
|
||||
return encodeURI(`/repositories/${encodeURIComponent(repositoryName)}`);
|
||||
}
|
||||
|
||||
export function linkToEditRepository(repositoryName: string) {
|
||||
return `/edit_repository/${encodeURIComponent(repositoryName)}`;
|
||||
return encodeURI(`/edit_repository/${encodeURIComponent(repositoryName)}`);
|
||||
}
|
||||
|
||||
export function linkToAddRepository(redirect?: string) {
|
||||
return `/add_repository${redirect ? `?redirect=${encodeURIComponent(redirect)}` : ''}`;
|
||||
return encodeURI(`/add_repository${redirect ? `?redirect=${encodeURIComponent(redirect)}` : ''}`);
|
||||
}
|
||||
|
||||
export function linkToSnapshots(repositoryName?: string, policyName?: string) {
|
||||
if (repositoryName) {
|
||||
return `/snapshots?repository=${encodeURIComponent(repositoryName)}`;
|
||||
return encodeURI(`/snapshots?repository=${encodeURIComponent(repositoryName)}`);
|
||||
}
|
||||
if (policyName) {
|
||||
return `/snapshots?policy=${encodeURIComponent(policyName)}`;
|
||||
return encodeURI(`/snapshots?policy=${encodeURIComponent(policyName)}`);
|
||||
}
|
||||
return `/snapshots`;
|
||||
}
|
||||
|
||||
export function linkToSnapshot(repositoryName: string, snapshotName: string) {
|
||||
return `/snapshots/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}`;
|
||||
return encodeURI(
|
||||
`/snapshots/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}`
|
||||
);
|
||||
}
|
||||
|
||||
export function linkToRestoreSnapshot(repositoryName: string, snapshotName: string) {
|
||||
return `/restore/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}`;
|
||||
return encodeURI(
|
||||
`/restore/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}`
|
||||
);
|
||||
}
|
||||
|
||||
export function linkToPolicies() {
|
||||
|
@ -47,11 +51,11 @@ export function linkToPolicies() {
|
|||
}
|
||||
|
||||
export function linkToPolicy(policyName: string) {
|
||||
return `/policies/${encodeURIComponent(policyName)}`;
|
||||
return encodeURI(`/policies/${encodeURIComponent(policyName)}`);
|
||||
}
|
||||
|
||||
export function linkToEditPolicy(policyName: string) {
|
||||
return `/edit_policy/${encodeURIComponent(policyName)}`;
|
||||
return encodeURI(`/edit_policy/${encodeURIComponent(policyName)}`);
|
||||
}
|
||||
|
||||
export function linkToAddPolicy() {
|
||||
|
|
|
@ -12,4 +12,4 @@ export {
|
|||
|
||||
export { RestoreValidation, validateRestore } from './validate_restore';
|
||||
|
||||
export { PolicyValidation, validatePolicy } from './validate_policy';
|
||||
export { PolicyValidation, validatePolicy, ValidatePolicyHelperData } from './validate_policy';
|
||||
|
|
|
@ -25,21 +25,32 @@ const isSnapshotNameNotLowerCase = (str: string): boolean => {
|
|||
return strExcludeDate !== strExcludeDate.toLowerCase() ? true : false;
|
||||
};
|
||||
|
||||
export interface ValidatePolicyHelperData {
|
||||
managedRepository?: {
|
||||
name: string;
|
||||
policy: string;
|
||||
};
|
||||
isEditing?: boolean;
|
||||
policyName?: string;
|
||||
/**
|
||||
* Whether to block on the indices configured for this snapshot.
|
||||
*
|
||||
* By default ES will back up all indices and data streams if this is an empty array or left blank.
|
||||
* However, in the UI, under certain conditions, like when displaying indices to select for backup,
|
||||
* we want to block users from submitting an empty array, but not block the entire form if they
|
||||
* are not configuring this value - like when they are on a previous step.
|
||||
*/
|
||||
validateIndicesCount?: boolean;
|
||||
}
|
||||
|
||||
export const validatePolicy = (
|
||||
policy: SlmPolicyPayload,
|
||||
validationHelperData: {
|
||||
managedRepository?: {
|
||||
name: string;
|
||||
policy: string;
|
||||
};
|
||||
isEditing?: boolean;
|
||||
policyName?: string;
|
||||
}
|
||||
validationHelperData: ValidatePolicyHelperData
|
||||
): PolicyValidation => {
|
||||
const i18n = textService.i18n;
|
||||
|
||||
const { name, snapshotName, schedule, repository, config, retention } = policy;
|
||||
const { managedRepository, isEditing, policyName } = validationHelperData;
|
||||
const { managedRepository, isEditing, policyName, validateIndicesCount } = validationHelperData;
|
||||
|
||||
const validation: PolicyValidation = {
|
||||
isValid: true,
|
||||
|
@ -96,7 +107,12 @@ export const validatePolicy = (
|
|||
);
|
||||
}
|
||||
|
||||
if (config && typeof config.indices === 'string' && config.indices.trim().length === 0) {
|
||||
if (
|
||||
validateIndicesCount &&
|
||||
config &&
|
||||
typeof config.indices === 'string' &&
|
||||
config.indices.trim().length === 0
|
||||
) {
|
||||
validation.errors.indices.push(
|
||||
i18n.translate('xpack.snapshotRestore.policyValidation.indexPatternRequiredErrorMessage', {
|
||||
defaultMessage: 'At least one index pattern is required.',
|
||||
|
@ -104,7 +120,12 @@ export const validatePolicy = (
|
|||
);
|
||||
}
|
||||
|
||||
if (config && Array.isArray(config.indices) && config.indices.length === 0) {
|
||||
if (
|
||||
validateIndicesCount &&
|
||||
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 data stream or index.',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue