mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
* Add new Policies tab * Allow config to be passed to common callWithRequestFactory * Add endpoints to retrieve slm policy(ies) * add typing and deserialization for policy last success and failure details * add policy list table * add basic policy details, link to repository and filtered snapshots * Add policy details view * Convert hardcoded links to use navigation service * link to policy details from snapshot details and change snapshot table filtering logic to exact match * Address PR feedback
This commit is contained in:
parent
eb44e9b99c
commit
b9fd8ba33a
47 changed files with 1800 additions and 88 deletions
|
@ -4,4 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
export { flatten } from './flatten';
|
||||
export { serializeRestoreSettings } from './restore_settings_serialization';
|
||||
export {
|
||||
deserializeRestoreSettings,
|
||||
serializeRestoreSettings,
|
||||
} from './restore_settings_serialization';
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { serializeRestoreSettings } from './restore_settings_serialization';
|
||||
import {
|
||||
deserializeRestoreSettings,
|
||||
serializeRestoreSettings,
|
||||
} from './restore_settings_serialization';
|
||||
|
||||
describe('restore_settings_serialization()', () => {
|
||||
it('should serialize blank restore settings', () => {
|
||||
|
@ -35,6 +38,7 @@ describe('restore_settings_serialization()', () => {
|
|||
partial: true,
|
||||
indexSettings: '{"modified_setting":123}',
|
||||
ignoreIndexSettings: ['setting1'],
|
||||
ignoreUnavailable: true,
|
||||
})
|
||||
).toEqual({
|
||||
indices: ['foo', 'bar'],
|
||||
|
@ -44,6 +48,7 @@ describe('restore_settings_serialization()', () => {
|
|||
partial: true,
|
||||
index_settings: { modified_setting: 123 },
|
||||
ignore_index_settings: ['setting1'],
|
||||
ignore_unavailable: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -54,4 +59,47 @@ describe('restore_settings_serialization()', () => {
|
|||
})
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it('should deserialize blank restore settings', () => {
|
||||
expect(deserializeRestoreSettings({})).toEqual({});
|
||||
});
|
||||
|
||||
it('should deserialize partial restore settings', () => {
|
||||
expect(deserializeRestoreSettings({})).toEqual({});
|
||||
expect(
|
||||
deserializeRestoreSettings({
|
||||
indices: ['foo', 'bar'],
|
||||
ignore_index_settings: ['setting1'],
|
||||
partial: true,
|
||||
})
|
||||
).toEqual({
|
||||
indices: ['foo', 'bar'],
|
||||
ignoreIndexSettings: ['setting1'],
|
||||
partial: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should deserialize full restore settings', () => {
|
||||
expect(
|
||||
deserializeRestoreSettings({
|
||||
indices: ['foo', 'bar'],
|
||||
rename_pattern: 'capture_pattern',
|
||||
rename_replacement: 'replacement_pattern',
|
||||
include_global_state: true,
|
||||
partial: true,
|
||||
index_settings: { modified_setting: 123 },
|
||||
ignore_index_settings: ['setting1'],
|
||||
ignore_unavailable: true,
|
||||
})
|
||||
).toEqual({
|
||||
indices: ['foo', 'bar'],
|
||||
renamePattern: 'capture_pattern',
|
||||
renameReplacement: 'replacement_pattern',
|
||||
includeGlobalState: true,
|
||||
partial: true,
|
||||
indexSettings: '{"modified_setting":123}',
|
||||
ignoreIndexSettings: ['setting1'],
|
||||
ignoreUnavailable: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,6 +23,7 @@ export function serializeRestoreSettings(restoreSettings: RestoreSettings): Rest
|
|||
partial,
|
||||
indexSettings,
|
||||
ignoreIndexSettings,
|
||||
ignoreUnavailable,
|
||||
} = restoreSettings;
|
||||
|
||||
let parsedIndexSettings: RestoreSettingsEs['index_settings'] | undefined;
|
||||
|
@ -43,6 +44,33 @@ export function serializeRestoreSettings(restoreSettings: RestoreSettings): Rest
|
|||
partial,
|
||||
index_settings: parsedIndexSettings,
|
||||
ignore_index_settings: ignoreIndexSettings,
|
||||
ignore_unavailable: ignoreUnavailable,
|
||||
};
|
||||
|
||||
return removeUndefinedSettings(settings);
|
||||
}
|
||||
|
||||
export function deserializeRestoreSettings(restoreSettingsEs: RestoreSettingsEs): RestoreSettings {
|
||||
const {
|
||||
indices,
|
||||
rename_pattern: renamePattern,
|
||||
rename_replacement: renameReplacement,
|
||||
include_global_state: includeGlobalState,
|
||||
partial,
|
||||
index_settings: indexSettings,
|
||||
ignore_index_settings: ignoreIndexSettings,
|
||||
ignore_unavailable: ignoreUnavailable,
|
||||
} = restoreSettingsEs;
|
||||
|
||||
const settings: RestoreSettings = {
|
||||
indices,
|
||||
renamePattern,
|
||||
renameReplacement,
|
||||
includeGlobalState,
|
||||
partial,
|
||||
indexSettings: indexSettings ? JSON.stringify(indexSettings) : undefined,
|
||||
ignoreIndexSettings,
|
||||
ignoreUnavailable,
|
||||
};
|
||||
|
||||
return removeUndefinedSettings(settings);
|
||||
|
|
|
@ -8,3 +8,4 @@ export * from './app';
|
|||
export * from './repository';
|
||||
export * from './snapshot';
|
||||
export * from './restore';
|
||||
export * from './policy';
|
||||
|
|
|
@ -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 { SnapshotConfig, SnapshotConfigEs } from './snapshot';
|
||||
|
||||
export interface SlmPolicy {
|
||||
name: string;
|
||||
version: number;
|
||||
modifiedDate: string;
|
||||
modifiedDateMillis: number;
|
||||
snapshotName: string;
|
||||
schedule: string;
|
||||
repository: string;
|
||||
config: SnapshotConfig;
|
||||
nextExecution: string;
|
||||
nextExecutionMillis: number;
|
||||
lastSuccess?: {
|
||||
snapshotName: string;
|
||||
timeString: string;
|
||||
time: number;
|
||||
};
|
||||
lastFailure?: {
|
||||
snapshotName: string;
|
||||
timeString: string;
|
||||
time: number;
|
||||
details: object | string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SlmPolicyEs {
|
||||
version: number;
|
||||
modified_date: string;
|
||||
modified_date_millis: number;
|
||||
policy: {
|
||||
name: string;
|
||||
schedule: string;
|
||||
repository: string;
|
||||
config: SnapshotConfigEs;
|
||||
};
|
||||
next_execution: string;
|
||||
next_execution_millis: number;
|
||||
last_success?: {
|
||||
snapshot_name: string;
|
||||
time_string: string;
|
||||
time: number;
|
||||
};
|
||||
last_failure?: {
|
||||
snapshot_name: string;
|
||||
time_string: string;
|
||||
time: number;
|
||||
details: string;
|
||||
};
|
||||
}
|
|
@ -12,6 +12,7 @@ export interface RestoreSettings {
|
|||
partial?: boolean;
|
||||
indexSettings?: string;
|
||||
ignoreIndexSettings?: string[];
|
||||
ignoreUnavailable?: boolean;
|
||||
}
|
||||
|
||||
export interface RestoreSettingsEs {
|
||||
|
@ -22,6 +23,7 @@ export interface RestoreSettingsEs {
|
|||
partial?: boolean;
|
||||
index_settings?: { [key: string]: any };
|
||||
ignore_index_settings?: string[];
|
||||
ignore_unavailable?: boolean;
|
||||
}
|
||||
|
||||
export interface SnapshotRestore {
|
||||
|
|
|
@ -3,6 +3,25 @@
|
|||
* 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 SnapshotConfig {
|
||||
indices?: string[];
|
||||
ignoreUnavailable?: boolean;
|
||||
includeGlobalState?: boolean;
|
||||
partial?: boolean;
|
||||
metadata?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SnapshotConfigEs {
|
||||
indices?: string[];
|
||||
ignore_unavailable?: boolean;
|
||||
include_global_state?: boolean;
|
||||
partial?: boolean;
|
||||
metadata?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SnapshotDetails {
|
||||
repository: string;
|
||||
|
@ -23,6 +42,30 @@ export interface SnapshotDetails {
|
|||
indexFailures: any[];
|
||||
shards: SnapshotDetailsShardsStatus;
|
||||
isManagedRepository?: boolean;
|
||||
policyName?: string;
|
||||
}
|
||||
|
||||
export interface SnapshotDetailsEs {
|
||||
snapshot: string;
|
||||
uuid: string;
|
||||
version_id: number;
|
||||
version: string;
|
||||
indices: string[];
|
||||
include_global_state: boolean;
|
||||
state: string;
|
||||
/** e.g. '2019-04-05T21:56:40.438Z' */
|
||||
start_time: string;
|
||||
start_time_in_millis: number;
|
||||
/** e.g. '2019-04-05T21:56:45.210Z' */
|
||||
end_time: string;
|
||||
end_time_in_millis: number;
|
||||
duration_in_millis: number;
|
||||
failures: any[];
|
||||
shards: SnapshotDetailsShardsStatusEs;
|
||||
metadata?: {
|
||||
policy: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
interface SnapshotDetailsShardsStatus {
|
||||
|
@ -30,3 +73,9 @@ interface SnapshotDetailsShardsStatus {
|
|||
failed: number;
|
||||
successful: number;
|
||||
}
|
||||
|
||||
interface SnapshotDetailsShardsStatusEs {
|
||||
total: number;
|
||||
failed: number;
|
||||
successful: number;
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ export const App: React.FunctionComponent = () => {
|
|||
);
|
||||
}
|
||||
|
||||
const sections: Section[] = ['repositories', 'snapshots', 'restore_status'];
|
||||
const sections: Section[] = ['repositories', 'snapshots', 'restore_status', 'policies'];
|
||||
const sectionsRegex = sections.join('|');
|
||||
|
||||
return (
|
||||
|
|
|
@ -9,9 +9,10 @@ import { useAppDependencies } from '../index';
|
|||
|
||||
interface Props {
|
||||
epochMs: number;
|
||||
type?: 'date' | 'time';
|
||||
}
|
||||
|
||||
export const FormattedDateTime: React.FunctionComponent<Props> = ({ epochMs }) => {
|
||||
export const FormattedDateTime: React.FunctionComponent<Props> = ({ epochMs, type }) => {
|
||||
const {
|
||||
core: {
|
||||
i18n: { FormattedDate, FormattedTime },
|
||||
|
@ -19,11 +20,16 @@ export const FormattedDateTime: React.FunctionComponent<Props> = ({ epochMs }) =
|
|||
} = useAppDependencies();
|
||||
|
||||
const date = new Date(epochMs);
|
||||
const formattedDate = <FormattedDate value={date} year="numeric" month="short" day="2-digit" />;
|
||||
const formattedTime = <FormattedTime value={date} timeZoneName="short" />;
|
||||
|
||||
if (type) {
|
||||
return type === 'date' ? formattedDate : formattedTime;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<FormattedDate value={date} year="numeric" month="short" day="2-digit" />{' '}
|
||||
<FormattedTime value={date} timeZoneName="short" />
|
||||
{formattedDate} {formattedTime}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
export const BASE_PATH = '/management/elasticsearch/snapshot_restore';
|
||||
export const DEFAULT_SECTION: Section = 'snapshots';
|
||||
export type Section = 'repositories' | 'snapshots' | 'restore_status';
|
||||
export type Section = 'repositories' | 'snapshots' | 'restore_status' | 'policies';
|
||||
|
||||
// Set a minimum request duration to avoid strange UI flickers
|
||||
export const MINIMUM_TIMEOUT_MS = 300;
|
||||
|
@ -105,3 +105,7 @@ export const UIM_SNAPSHOT_DELETE_MANY = 'snapshot_delete_many';
|
|||
export const UIM_RESTORE_CREATE = 'restore_create';
|
||||
export const UIM_RESTORE_LIST_LOAD = 'restore_list_load';
|
||||
export const UIM_RESTORE_LIST_EXPAND_INDEX = 'restore_list_expand_index';
|
||||
export const UIM_POLICY_LIST_LOAD = 'policy_list_load';
|
||||
export const UIM_POLICY_SHOW_DETAILS_CLICK = 'policy_show_details_click';
|
||||
export const UIM_POLICY_DETAIL_PANEL_SUMMARY_TAB = 'policy_detail_panel_summary_tab';
|
||||
export const UIM_POLICY_DETAIL_PANEL_HISTORY_TAB = 'policy_detail_panel_last_success_tab';
|
||||
|
|
|
@ -27,6 +27,7 @@ import { breadcrumbService } from '../../services/navigation';
|
|||
import { RepositoryList } from './repository_list';
|
||||
import { SnapshotList } from './snapshot_list';
|
||||
import { RestoreList } from './restore_list';
|
||||
import { PolicyList } from './policy_list';
|
||||
import { documentationLinksService } from '../../services/documentation';
|
||||
|
||||
interface MatchParams {
|
||||
|
@ -67,6 +68,15 @@ export const SnapshotRestoreHome: React.FunctionComponent<RouteComponentProps<Ma
|
|||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'policies',
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.home.policiesTabTitle"
|
||||
defaultMessage="Policies"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'restore_status',
|
||||
name: (
|
||||
|
@ -158,6 +168,7 @@ export const SnapshotRestoreHome: React.FunctionComponent<RouteComponentProps<Ma
|
|||
component={SnapshotList}
|
||||
/>
|
||||
<Route exact path={`${BASE_PATH}/restore_status`} component={RestoreList} />
|
||||
<Route exact path={`${BASE_PATH}/policies/:policyName*`} component={PolicyList} />
|
||||
</Switch>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
|
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './snapshot';
|
||||
export { PolicyList } from './policy_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 { PolicyDetails } from './policy_details';
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* 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, useEffect } from 'react';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiTitle,
|
||||
EuiTabs,
|
||||
EuiTab,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { SlmPolicy } from '../../../../../../common/types';
|
||||
import { useAppDependencies } from '../../../../index';
|
||||
import {
|
||||
UIM_POLICY_DETAIL_PANEL_SUMMARY_TAB,
|
||||
UIM_POLICY_DETAIL_PANEL_HISTORY_TAB,
|
||||
} from '../../../../constants';
|
||||
import { useLoadPolicy } from '../../../../services/http';
|
||||
import { uiMetricService } from '../../../../services/ui_metric';
|
||||
|
||||
import { SectionError, SectionLoading } from '../../../../components';
|
||||
import { TabSummary, TabHistory } from './tabs';
|
||||
|
||||
interface Props {
|
||||
policyName: SlmPolicy['name'];
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const TAB_SUMMARY = 'summary';
|
||||
const TAB_HISTORY = 'success';
|
||||
|
||||
const tabToUiMetricMap: { [key: string]: string } = {
|
||||
[TAB_SUMMARY]: UIM_POLICY_DETAIL_PANEL_SUMMARY_TAB,
|
||||
[TAB_HISTORY]: UIM_POLICY_DETAIL_PANEL_HISTORY_TAB,
|
||||
};
|
||||
|
||||
export const PolicyDetails: React.FunctionComponent<Props> = ({ policyName, onClose }) => {
|
||||
const {
|
||||
core: { i18n },
|
||||
} = useAppDependencies();
|
||||
|
||||
const { FormattedMessage } = i18n;
|
||||
const { trackUiMetric } = uiMetricService;
|
||||
const { error, data: policyDetails } = useLoadPolicy(policyName);
|
||||
const [activeTab, setActiveTab] = useState<string>(TAB_SUMMARY);
|
||||
|
||||
// Reset tab when we look at a different policy
|
||||
useEffect(() => {
|
||||
setActiveTab(TAB_SUMMARY);
|
||||
}, [policyName]);
|
||||
|
||||
const tabOptions = [
|
||||
{
|
||||
id: TAB_SUMMARY,
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.summaryTabTitle"
|
||||
defaultMessage="Summary"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: TAB_HISTORY,
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.historyTabTitle"
|
||||
defaultMessage="History"
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const renderTabs = () => (
|
||||
<EuiTabs>
|
||||
{tabOptions.map(tab => (
|
||||
<EuiTab
|
||||
onClick={() => {
|
||||
trackUiMetric(tabToUiMetricMap[tab.id]);
|
||||
setActiveTab(tab.id);
|
||||
}}
|
||||
isSelected={tab.id === activeTab}
|
||||
key={tab.id}
|
||||
data-test-subj="tab"
|
||||
>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
))}
|
||||
</EuiTabs>
|
||||
);
|
||||
|
||||
const renderBody = () => {
|
||||
if (policyDetails) {
|
||||
const { policy } = policyDetails;
|
||||
|
||||
switch (activeTab) {
|
||||
case TAB_HISTORY:
|
||||
return <TabHistory policy={policy} />;
|
||||
default:
|
||||
return <TabSummary policy={policy} />;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
return renderError();
|
||||
}
|
||||
return renderLoading();
|
||||
};
|
||||
|
||||
const renderLoading = () => {
|
||||
return (
|
||||
<SectionLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.loadingPolicyDescription"
|
||||
defaultMessage="Loading policy…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
);
|
||||
};
|
||||
|
||||
const renderError = () => {
|
||||
const notFound = error.status === 404;
|
||||
const errorObject = notFound
|
||||
? {
|
||||
data: {
|
||||
error: i18n.translate(
|
||||
'xpack.snapshotRestore.policyDetails.policyNotFoundErrorMessage',
|
||||
{
|
||||
defaultMessage: `The policy '{name}' does not exist.`,
|
||||
values: {
|
||||
name: policyName,
|
||||
},
|
||||
}
|
||||
),
|
||||
},
|
||||
}
|
||||
: error;
|
||||
return (
|
||||
<SectionError
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.loadingPolicyErrorTitle"
|
||||
defaultMessage="Error loading policy"
|
||||
/>
|
||||
}
|
||||
error={errorObject}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
iconType="cross"
|
||||
flush="left"
|
||||
onClick={onClose}
|
||||
data-test-subj="srPolicyDetailsFlyoutCloseButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.closeButtonLabel"
|
||||
defaultMessage="Close"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlyout
|
||||
onClose={onClose}
|
||||
data-test-subj="policyDetail"
|
||||
aria-labelledby="srPolicyDetailsFlyoutTitle"
|
||||
size="m"
|
||||
maxWidth={400}
|
||||
>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle size="m">
|
||||
<h2 id="srPolicyDetailsFlyoutTitle" data-test-subj="title">
|
||||
{policyName}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
{renderTabs()}
|
||||
</EuiFlyoutHeader>
|
||||
|
||||
<EuiFlyoutBody data-test-subj="content">{renderBody()}</EuiFlyoutBody>
|
||||
|
||||
<EuiFlyoutFooter>{renderFooter()}</EuiFlyoutFooter>
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
|
@ -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 { TabSummary } from './tab_summary';
|
||||
export { TabHistory } from './tab_history';
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* 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 } from 'react';
|
||||
import {
|
||||
EuiCodeEditor,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLink,
|
||||
EuiTitle,
|
||||
EuiDescriptionList,
|
||||
EuiDescriptionListTitle,
|
||||
EuiDescriptionListDescription,
|
||||
EuiText,
|
||||
EuiHorizontalRule,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { SlmPolicy } from '../../../../../../../common/types';
|
||||
import { useAppDependencies } from '../../../../../index';
|
||||
import { FormattedDateTime } from '../../../../../components';
|
||||
import { linkToSnapshot } from '../../../../../services/navigation';
|
||||
|
||||
interface Props {
|
||||
policy: SlmPolicy;
|
||||
}
|
||||
|
||||
export const TabHistory: React.FunctionComponent<Props> = ({ policy }) => {
|
||||
const {
|
||||
core: { i18n },
|
||||
} = useAppDependencies();
|
||||
const { FormattedMessage } = i18n;
|
||||
|
||||
const { lastSuccess, lastFailure, nextExecutionMillis, name, repository } = policy;
|
||||
|
||||
const renderLastSuccess = () => {
|
||||
if (!lastSuccess) {
|
||||
return null;
|
||||
}
|
||||
const { time, snapshotName } = lastSuccess;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.lastSuccessTitle"
|
||||
defaultMessage="Last successful snapshot"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiDescriptionList textStyle="reverse">
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="successTime">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.lastSuccess.timeLabel"
|
||||
defaultMessage="Succeeded on"
|
||||
description="Title for date time. Example: Succeeded on Jul 16, 2019 6:30 PM PDT"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
<FormattedDateTime epochMs={time} />
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem data-test-subj="successSnapshot">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.lastSuccess.snapshotNameLabel"
|
||||
defaultMessage="Snapshot name"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
<EuiLink href={linkToSnapshot(repository, snapshotName)}>{snapshotName}</EuiLink>
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiDescriptionList>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const renderLastFailure = () => {
|
||||
if (!lastFailure) {
|
||||
return null;
|
||||
}
|
||||
const { time, snapshotName, details } = lastFailure;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.lastFailureTitle"
|
||||
defaultMessage="Last snapshot failure"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiDescriptionList textStyle="reverse">
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="failureTime">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.lastFailure.timeLabel"
|
||||
defaultMessage="Failed on"
|
||||
description="Title for date time. Example: Failed on Jul 16, 2019 6:30 PM PDT"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
<FormattedDateTime epochMs={time} />
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem data-test-subj="failureSnapshot">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.lastFailure.snapshotNameLabel"
|
||||
defaultMessage="Snapshot name"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
{snapshotName}
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="failureDetails">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.lastFailure.detailsLabel"
|
||||
defaultMessage="Failure details"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
<EuiCodeEditor
|
||||
mode="json"
|
||||
theme="textmate"
|
||||
width="100%"
|
||||
isReadOnly
|
||||
value={JSON.stringify(details, null, 2)}
|
||||
setOptions={{
|
||||
showLineNumbers: false,
|
||||
tabSize: 2,
|
||||
maxLines: Infinity,
|
||||
}}
|
||||
editorProps={{
|
||||
$blockScrolling: Infinity,
|
||||
}}
|
||||
minLines={6}
|
||||
maxLines={6}
|
||||
showGutter={false}
|
||||
aria-label={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.lastFailure.detailsAriaLabel"
|
||||
defaultMessage="Last failure details for policy '{name}'"
|
||||
values={{
|
||||
name,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiDescriptionList>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
return lastSuccess || lastFailure ? (
|
||||
<Fragment>
|
||||
{renderLastSuccess()}
|
||||
{lastSuccess && lastFailure ? <EuiHorizontalRule /> : null}
|
||||
{renderLastFailure()}
|
||||
</Fragment>
|
||||
) : (
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.noHistoryMessage"
|
||||
defaultMessage="This policy has not been executed yet. It will automatically run on {date} at {time}."
|
||||
values={{
|
||||
date: <FormattedDateTime epochMs={nextExecutionMillis} type="date" />,
|
||||
time: <FormattedDateTime epochMs={nextExecutionMillis} type="time" />,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLink,
|
||||
EuiTitle,
|
||||
EuiDescriptionList,
|
||||
EuiDescriptionListTitle,
|
||||
EuiDescriptionListDescription,
|
||||
EuiIcon,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { SlmPolicy } from '../../../../../../../common/types';
|
||||
import { useAppDependencies } from '../../../../../index';
|
||||
import { FormattedDateTime } from '../../../../../components';
|
||||
import { linkToSnapshots, linkToRepository } from '../../../../../services/navigation';
|
||||
|
||||
interface Props {
|
||||
policy: SlmPolicy;
|
||||
}
|
||||
|
||||
export const TabSummary: React.FunctionComponent<Props> = ({ policy }) => {
|
||||
const {
|
||||
core: { i18n },
|
||||
} = useAppDependencies();
|
||||
const { FormattedMessage } = i18n;
|
||||
|
||||
const {
|
||||
version,
|
||||
name,
|
||||
modifiedDateMillis,
|
||||
snapshotName,
|
||||
repository,
|
||||
schedule,
|
||||
nextExecutionMillis,
|
||||
config,
|
||||
} = policy;
|
||||
const { includeGlobalState, ignoreUnavailable, indices, partial } = config;
|
||||
|
||||
// Only show 10 indices initially
|
||||
const [isShowingFullIndicesList, setIsShowingFullIndicesList] = useState<boolean>(false);
|
||||
const hiddenIndicesCount = indices && indices.length > 10 ? indices.length - 10 : 0;
|
||||
const shortIndicesList =
|
||||
indices && indices.length ? (
|
||||
<ul>
|
||||
{[...indices].splice(0, 10).map((index: string) => (
|
||||
<li key={index}>
|
||||
<EuiTitle size="xs">
|
||||
<span>{index}</span>
|
||||
</EuiTitle>
|
||||
</li>
|
||||
))}
|
||||
{hiddenIndicesCount ? (
|
||||
<li key="hiddenIndicesCount">
|
||||
<EuiTitle size="xs">
|
||||
<EuiLink onClick={() => setIsShowingFullIndicesList(true)}>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.indicesShowAllLink"
|
||||
defaultMessage="Show {count} more {count, plural, one {index} other {indices}}"
|
||||
values={{ count: hiddenIndicesCount }}
|
||||
/>{' '}
|
||||
<EuiIcon type="arrowDown" />
|
||||
</EuiLink>
|
||||
</EuiTitle>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.allIndicesLabel"
|
||||
defaultMessage="All indices"
|
||||
/>
|
||||
);
|
||||
const fullIndicesList =
|
||||
indices && indices.length && indices.length > 10 ? (
|
||||
<ul>
|
||||
{indices.map((index: string) => (
|
||||
<li key={index}>
|
||||
<EuiTitle size="xs">
|
||||
<span>{index}</span>
|
||||
</EuiTitle>
|
||||
</li>
|
||||
))}
|
||||
{hiddenIndicesCount ? (
|
||||
<li key="hiddenIndicesCount">
|
||||
<EuiTitle size="xs">
|
||||
<EuiLink onClick={() => setIsShowingFullIndicesList(false)}>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.indicesCollapseAllLink"
|
||||
defaultMessage="Hide {count, plural, one {# index} other {# indices}}"
|
||||
values={{ count: hiddenIndicesCount }}
|
||||
/>{' '}
|
||||
<EuiIcon type="arrowUp" />
|
||||
</EuiLink>
|
||||
</EuiTitle>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<EuiDescriptionList textStyle="reverse">
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="version">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.versionLabel"
|
||||
defaultMessage="Version"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
{version}
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem data-test-subj="modified">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.modifiedDateLabel"
|
||||
defaultMessage="Last modified"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
<FormattedDateTime epochMs={modifiedDateMillis} />
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="name">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.snapshotNameLabel"
|
||||
defaultMessage="Snapshot name"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
<EuiLink href={linkToSnapshots(undefined, name)}>{snapshotName}</EuiLink>
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem data-test-subj="repository">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.repositoryLabel"
|
||||
defaultMessage="Repository"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
<EuiLink href={linkToRepository(repository)}>{repository}</EuiLink>
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="schedule">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.scheduleLabel"
|
||||
defaultMessage="Schedule"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
{schedule}
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem data-test-subj="execution">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.nextExecutionLabel"
|
||||
defaultMessage="Next execution"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
<FormattedDateTime epochMs={nextExecutionMillis} />
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="indices">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.indicesLabel"
|
||||
defaultMessage="Indices"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
<EuiText>{isShowingFullIndicesList ? fullIndicesList : shortIndicesList}</EuiText>
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem data-test-subj="includeGlobalState">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.ignoreUnavailableLabel"
|
||||
defaultMessage="Ignore unavailable indices"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
{ignoreUnavailable ? (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.ignoreUnavailableTrueLabel"
|
||||
defaultMessage="Yes"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.ignoreUnavailableFalseLabel"
|
||||
defaultMessage="No"
|
||||
/>
|
||||
)}
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="partial">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.partialLabel"
|
||||
defaultMessage="Allow partial shards"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
{partial ? (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.partialTrueLabel"
|
||||
defaultMessage="Yes"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.partialFalseLabel"
|
||||
defaultMessage="No"
|
||||
/>
|
||||
)}
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem data-test-subj="includeGlobalState">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.includeGlobalStateLabel"
|
||||
defaultMessage="Include global state"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
{includeGlobalState === false ? (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.includeGlobalStateFalseLabel"
|
||||
defaultMessage="No"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyDetails.includeGlobalStateTrueLabel"
|
||||
defaultMessage="Yes"
|
||||
/>
|
||||
)}
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiDescriptionList>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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, useEffect } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { SlmPolicy } from '../../../../../common/types';
|
||||
import { SectionError, SectionLoading } from '../../../components';
|
||||
import { BASE_PATH, UIM_POLICY_LIST_LOAD } from '../../../constants';
|
||||
import { useAppDependencies } from '../../../index';
|
||||
import { useLoadPolicies } from '../../../services/http';
|
||||
import { uiMetricService } from '../../../services/ui_metric';
|
||||
|
||||
import { PolicyDetails } from './policy_details';
|
||||
import { PolicyTable } from './policy_table';
|
||||
|
||||
interface MatchParams {
|
||||
policyName?: SlmPolicy['name'];
|
||||
}
|
||||
|
||||
export const PolicyList: React.FunctionComponent<RouteComponentProps<MatchParams>> = ({
|
||||
match: {
|
||||
params: { policyName },
|
||||
},
|
||||
history,
|
||||
}) => {
|
||||
const {
|
||||
core: {
|
||||
i18n: { FormattedMessage },
|
||||
},
|
||||
} = useAppDependencies();
|
||||
|
||||
const {
|
||||
error,
|
||||
loading,
|
||||
data: { policies } = {
|
||||
policies: undefined,
|
||||
},
|
||||
request: reload,
|
||||
} = useLoadPolicies();
|
||||
|
||||
const openPolicyDetailsUrl = (newPolicyName: SlmPolicy['name']): string => {
|
||||
return history.createHref({
|
||||
pathname: `${BASE_PATH}/policies/${newPolicyName}`,
|
||||
});
|
||||
};
|
||||
|
||||
const closePolicyDetails = () => {
|
||||
history.push(`${BASE_PATH}/policies`);
|
||||
};
|
||||
|
||||
// Track component loaded
|
||||
const { trackUiMetric } = uiMetricService;
|
||||
useEffect(() => {
|
||||
trackUiMetric(UIM_POLICY_LIST_LOAD);
|
||||
}, []);
|
||||
|
||||
let content;
|
||||
|
||||
if (loading) {
|
||||
content = (
|
||||
<SectionLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyList.loadingPoliciesDescription"
|
||||
defaultMessage="Loading policies…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
);
|
||||
} else if (error) {
|
||||
content = (
|
||||
<SectionError
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyList.LoadingPoliciesErrorMessage"
|
||||
defaultMessage="Error loading policies"
|
||||
/>
|
||||
}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
} else if (policies && policies.length === 0) {
|
||||
content = (
|
||||
<EuiEmptyPrompt
|
||||
iconType="managementApp"
|
||||
title={
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyList.emptyPromptTitle"
|
||||
defaultMessage="You don't have any snapshot policies yet"
|
||||
/>
|
||||
</h1>
|
||||
}
|
||||
body={
|
||||
<Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyList.emptyPromptDescription"
|
||||
defaultMessage="Use policies to schedule automatic backups of your cluster."
|
||||
/>
|
||||
</p>
|
||||
</Fragment>
|
||||
}
|
||||
data-test-subj="emptyPrompt"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<PolicyTable
|
||||
policies={policies || []}
|
||||
reload={reload}
|
||||
openPolicyDetailsUrl={openPolicyDetailsUrl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section data-test-subj="policyList">
|
||||
{policyName ? <PolicyDetails policyName={policyName} onClose={closePolicyDetails} /> : null}
|
||||
{content}
|
||||
</section>
|
||||
);
|
||||
};
|
|
@ -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 { PolicyTable } from './policy_table';
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* 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 { EuiButton, EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, EuiLink } from '@elastic/eui';
|
||||
|
||||
import { SlmPolicy } from '../../../../../../common/types';
|
||||
import { UIM_POLICY_SHOW_DETAILS_CLICK } from '../../../../constants';
|
||||
import { useAppDependencies } from '../../../../index';
|
||||
import { FormattedDateTime } from '../../../../components';
|
||||
import { uiMetricService } from '../../../../services/ui_metric';
|
||||
|
||||
interface Props {
|
||||
policies: SlmPolicy[];
|
||||
reload: () => Promise<void>;
|
||||
openPolicyDetailsUrl: (name: SlmPolicy['name']) => string;
|
||||
}
|
||||
|
||||
export const PolicyTable: React.FunctionComponent<Props> = ({
|
||||
policies,
|
||||
reload,
|
||||
openPolicyDetailsUrl,
|
||||
}) => {
|
||||
const {
|
||||
core: { i18n },
|
||||
} = useAppDependencies();
|
||||
const { FormattedMessage } = i18n;
|
||||
const { trackUiMetric } = uiMetricService;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
field: 'name',
|
||||
name: i18n.translate('xpack.snapshotRestore.policyList.table.policyNameColumnTitle', {
|
||||
defaultMessage: 'Policy',
|
||||
}),
|
||||
truncateText: true,
|
||||
sortable: true,
|
||||
render: (name: SlmPolicy['name']) => {
|
||||
return (
|
||||
<EuiLink
|
||||
onClick={() => trackUiMetric(UIM_POLICY_SHOW_DETAILS_CLICK)}
|
||||
href={openPolicyDetailsUrl(name)}
|
||||
data-test-subj="policyLink"
|
||||
>
|
||||
{name}
|
||||
</EuiLink>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'snapshotName',
|
||||
name: i18n.translate('xpack.snapshotRestore.policyList.table.snapshotNameColumnTitle', {
|
||||
defaultMessage: 'Snapshot name',
|
||||
}),
|
||||
truncateText: true,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
field: 'repository',
|
||||
name: i18n.translate('xpack.snapshotRestore.policyList.table.repositoryColumnTitle', {
|
||||
defaultMessage: 'Repository',
|
||||
}),
|
||||
truncateText: true,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
field: 'schedule',
|
||||
name: i18n.translate('xpack.snapshotRestore.policyList.table.scheduleColumnTitle', {
|
||||
defaultMessage: 'Schedule',
|
||||
}),
|
||||
truncateText: true,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
field: 'nextExecutionMillis',
|
||||
name: i18n.translate('xpack.snapshotRestore.policyList.table.nextExecutionColumnTitle', {
|
||||
defaultMessage: 'Next execution',
|
||||
}),
|
||||
truncateText: true,
|
||||
sortable: true,
|
||||
render: (nextExecutionMillis: SlmPolicy['nextExecutionMillis']) => (
|
||||
<FormattedDateTime epochMs={nextExecutionMillis} />
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const sorting = {
|
||||
sort: {
|
||||
field: 'name',
|
||||
direction: 'asc',
|
||||
},
|
||||
};
|
||||
|
||||
const pagination = {
|
||||
initialPageSize: 20,
|
||||
pageSizeOptions: [10, 20, 50],
|
||||
};
|
||||
|
||||
const search = {
|
||||
toolsRight: (
|
||||
<EuiFlexGroup gutterSize="m" justifyContent="spaceAround">
|
||||
<EuiFlexItem>
|
||||
<EuiButton
|
||||
color="secondary"
|
||||
iconType="refresh"
|
||||
onClick={reload}
|
||||
data-test-subj="reloadButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.policyList.table.reloadPoliciesButton"
|
||||
defaultMessage="Reload"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
box: {
|
||||
incremental: true,
|
||||
schema: true,
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
type: 'field_value_selection',
|
||||
field: 'repository',
|
||||
name: i18n.translate('xpack.snapshotRestore.policyList.table.repositoryFilterLabel', {
|
||||
defaultMessage: 'Repository',
|
||||
}),
|
||||
multiSelect: false,
|
||||
options: Object.keys(
|
||||
policies.reduce((repositoriesMap: any, policy) => {
|
||||
repositoriesMap[policy.repository] = true;
|
||||
return repositoriesMap;
|
||||
}, {})
|
||||
).map(repository => {
|
||||
return {
|
||||
value: repository,
|
||||
view: repository,
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiInMemoryTable
|
||||
items={policies}
|
||||
itemId="name"
|
||||
columns={columns}
|
||||
search={search}
|
||||
sorting={sorting}
|
||||
pagination={pagination}
|
||||
isSelectable={true}
|
||||
rowProps={() => ({
|
||||
'data-test-subj': 'row',
|
||||
})}
|
||||
cellProps={() => ({
|
||||
'data-test-subj': 'cell',
|
||||
})}
|
||||
data-test-subj="policyTable"
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -30,7 +30,7 @@ import {
|
|||
verifyRepository as verifyRepositoryRequest,
|
||||
} from '../../../../services/http';
|
||||
import { textService } from '../../../../services/text';
|
||||
import { linkToSnapshots } from '../../../../services/navigation';
|
||||
import { linkToSnapshots, linkToEditRepository } from '../../../../services/navigation';
|
||||
|
||||
import { REPOSITORY_TYPES } from '../../../../../../common/constants';
|
||||
import { Repository, RepositoryVerification } from '../../../../../../common/types';
|
||||
|
@ -40,7 +40,6 @@ import {
|
|||
SectionLoading,
|
||||
RepositoryVerificationBadge,
|
||||
} from '../../../../components';
|
||||
import { BASE_PATH } from '../../../../constants';
|
||||
import { TypeDetails } from './type_details';
|
||||
|
||||
interface Props {
|
||||
|
@ -371,11 +370,7 @@ export const RepositoryDetails: React.FunctionComponent<Props> = ({
|
|||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
href={`#${BASE_PATH}/edit_repository/${repositoryName}`}
|
||||
fill
|
||||
color="primary"
|
||||
>
|
||||
<EuiButton href={linkToEditRepository(repositoryName)} fill color="primary">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.repositoryDetails.editButtonLabel"
|
||||
defaultMessage="Edit"
|
||||
|
|
|
@ -19,10 +19,11 @@ import {
|
|||
import { REPOSITORY_TYPES } from '../../../../../../common/constants';
|
||||
import { Repository, RepositoryType } from '../../../../../../common/types';
|
||||
import { RepositoryDeleteProvider } from '../../../../components';
|
||||
import { BASE_PATH, UIM_REPOSITORY_SHOW_DETAILS_CLICK } from '../../../../constants';
|
||||
import { UIM_REPOSITORY_SHOW_DETAILS_CLICK } from '../../../../constants';
|
||||
import { useAppDependencies } from '../../../../index';
|
||||
import { textService } from '../../../../services/text';
|
||||
import { uiMetricService } from '../../../../services/ui_metric';
|
||||
import { linkToEditRepository, linkToAddRepository } from '../../../../services/navigation';
|
||||
|
||||
interface Props {
|
||||
repositories: Repository[];
|
||||
|
@ -115,7 +116,7 @@ export const RepositoryTable: React.FunctionComponent<Props> = ({
|
|||
)}
|
||||
iconType="pencil"
|
||||
color="primary"
|
||||
href={`#${BASE_PATH}/edit_repository/${name}`}
|
||||
href={linkToEditRepository(name)}
|
||||
data-test-subj="editRepositoryButton"
|
||||
/>
|
||||
</EuiToolTip>
|
||||
|
@ -249,7 +250,7 @@ export const RepositoryTable: React.FunctionComponent<Props> = ({
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiButton
|
||||
href={`#${BASE_PATH}/add_repository`}
|
||||
href={linkToAddRepository()}
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
data-test-subj="registerRepositoryButton"
|
||||
|
@ -302,8 +303,8 @@ export const RepositoryTable: React.FunctionComponent<Props> = ({
|
|||
rowProps={() => ({
|
||||
'data-test-subj': 'row',
|
||||
})}
|
||||
cellProps={(item: any, column: any) => ({
|
||||
'data-test-subj': `cell`,
|
||||
cellProps={() => ({
|
||||
'data-test-subj': 'cell',
|
||||
})}
|
||||
data-test-subj="repositoryTable"
|
||||
/>
|
||||
|
|
|
@ -18,11 +18,12 @@ import {
|
|||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import { SectionError, SectionLoading } from '../../../components';
|
||||
import { UIM_RESTORE_LIST_LOAD, BASE_PATH } from '../../../constants';
|
||||
import { UIM_RESTORE_LIST_LOAD } from '../../../constants';
|
||||
import { useAppDependencies } from '../../../index';
|
||||
import { useLoadRestores } from '../../../services/http';
|
||||
import { useAppState } from '../../../services/state';
|
||||
import { uiMetricService } from '../../../services/ui_metric';
|
||||
import { linkToSnapshots } from '../../../services/navigation';
|
||||
import { RestoreTable } from './restore_table';
|
||||
|
||||
const ONE_SECOND_MS = 1000;
|
||||
|
@ -136,7 +137,7 @@ export const RestoreList: React.FunctionComponent = () => {
|
|||
defaultMessage="Go to {snapshotsLink} to start a restore."
|
||||
values={{
|
||||
snapshotsLink: (
|
||||
<EuiLink href={`#${BASE_PATH}/snapshots`}>
|
||||
<EuiLink href={linkToSnapshots()}>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreList.emptyPromptDescriptionLink"
|
||||
defaultMessage="Snapshots"
|
||||
|
|
|
@ -227,8 +227,8 @@ export const RestoreTable: React.FunctionComponent<Props> = ({ restores }) => {
|
|||
'data-test-subj': 'row',
|
||||
onClick: () => toggleIndexRestoreDetails(restore),
|
||||
})}
|
||||
cellProps={(item: any, column: any) => ({
|
||||
'data-test-subj': `cell`,
|
||||
cellProps={() => ({
|
||||
'data-test-subj': 'cell',
|
||||
})}
|
||||
data-test-subj="restoresTable"
|
||||
/>
|
||||
|
|
|
@ -22,16 +22,16 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import React, { Fragment, useState, useEffect } from 'react';
|
||||
|
||||
import { SnapshotDetails as ISnapshotDetails } from '../../../../../../common/types';
|
||||
import { SectionError, SectionLoading, SnapshotDeleteProvider } from '../../../../components';
|
||||
import { useAppDependencies } from '../../../../index';
|
||||
import {
|
||||
BASE_PATH,
|
||||
UIM_SNAPSHOT_DETAIL_PANEL_SUMMARY_TAB,
|
||||
UIM_SNAPSHOT_DETAIL_PANEL_FAILED_INDICES_TAB,
|
||||
SNAPSHOT_STATE,
|
||||
} from '../../../../constants';
|
||||
import { useLoadSnapshot } from '../../../../services/http';
|
||||
import { linkToRepository } from '../../../../services/navigation';
|
||||
import { linkToRepository, linkToRestoreSnapshot } from '../../../../services/navigation';
|
||||
import { uiMetricService } from '../../../../services/ui_metric';
|
||||
import { TabSummary, TabFailures } from './tabs';
|
||||
|
||||
|
@ -75,7 +75,7 @@ export const SnapshotDetails: React.FunctionComponent<Props> = ({
|
|||
let content;
|
||||
|
||||
if (snapshotDetails) {
|
||||
const { indexFailures, state: snapshotState } = snapshotDetails;
|
||||
const { indexFailures, state: snapshotState } = snapshotDetails as ISnapshotDetails;
|
||||
const tabOptions = [
|
||||
{
|
||||
id: TAB_SUMMARY,
|
||||
|
@ -221,7 +221,7 @@ export const SnapshotDetails: React.FunctionComponent<Props> = ({
|
|||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
href={`#${BASE_PATH}/restore/${repositoryName}/${snapshotId}`}
|
||||
href={linkToRestoreSnapshot(repositoryName, snapshotId)}
|
||||
fill
|
||||
color="primary"
|
||||
isDisabled={
|
||||
|
|
|
@ -19,13 +19,15 @@ import {
|
|||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { SnapshotDetails } from '../../../../../../../common/types';
|
||||
import { SNAPSHOT_STATE } from '../../../../../constants';
|
||||
import { useAppDependencies } from '../../../../../index';
|
||||
import { DataPlaceholder, FormattedDateTime } from '../../../../../components';
|
||||
import { linkToPolicy } from '../../../../../services/navigation';
|
||||
import { SnapshotState } from './snapshot_state';
|
||||
|
||||
interface Props {
|
||||
snapshotDetails: any;
|
||||
snapshotDetails: SnapshotDetails;
|
||||
}
|
||||
|
||||
export const TabSummary: React.SFC<Props> = ({ snapshotDetails }) => {
|
||||
|
@ -47,6 +49,7 @@ export const TabSummary: React.SFC<Props> = ({ snapshotDetails }) => {
|
|||
endTimeInMillis,
|
||||
durationInMillis,
|
||||
uuid,
|
||||
policyName,
|
||||
} = snapshotDetails;
|
||||
|
||||
// Only show 10 indices initially
|
||||
|
@ -253,6 +256,21 @@ export const TabSummary: React.SFC<Props> = ({ snapshotDetails }) => {
|
|||
)}
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
|
||||
{policyName ? (
|
||||
<EuiFlexItem data-test-subj="policy">
|
||||
<EuiDescriptionListTitle data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.snapshotDetails.createdByLabel"
|
||||
defaultMessage="Created by"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
|
||||
<EuiDescriptionListDescription className="eui-textBreakWord" data-test-subj="value">
|
||||
<EuiLink href={linkToPolicy(policyName)}>{policyName}</EuiLink>
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiDescriptionList>
|
||||
);
|
||||
|
|
|
@ -78,13 +78,19 @@ export const SnapshotList: React.FunctionComponent<RouteComponentProps<MatchPara
|
|||
}
|
||||
};
|
||||
|
||||
// Allow deeplinking to list pre-filtered by repository name
|
||||
// Allow deeplinking to list pre-filtered by repository name or by policy name
|
||||
const [filteredRepository, setFilteredRepository] = useState<string | undefined>(undefined);
|
||||
const [filteredPolicy, setFilteredPolicy] = useState<string | undefined>(undefined);
|
||||
useEffect(() => {
|
||||
if (search) {
|
||||
const parsedParams = parse(search.replace(/^\?/, ''));
|
||||
if (parsedParams.repository && parsedParams.repository !== filteredRepository) {
|
||||
setFilteredRepository(String(parsedParams.repository));
|
||||
const { repository, policy } = parsedParams;
|
||||
|
||||
if (policy && policy !== filteredPolicy) {
|
||||
setFilteredPolicy(String(policy));
|
||||
history.replace(`${BASE_PATH}/snapshots`);
|
||||
} else if (repository && repository !== filteredRepository) {
|
||||
setFilteredRepository(String(repository));
|
||||
history.replace(`${BASE_PATH}/snapshots`);
|
||||
}
|
||||
}
|
||||
|
@ -287,6 +293,7 @@ export const SnapshotList: React.FunctionComponent<RouteComponentProps<MatchPara
|
|||
openSnapshotDetailsUrl={openSnapshotDetailsUrl}
|
||||
onSnapshotDeleted={onSnapshotDeleted}
|
||||
repositoryFilter={filteredRepository}
|
||||
policyFilter={filteredPolicy}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
|
|
@ -16,9 +16,9 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { SnapshotDetails } from '../../../../../../common/types';
|
||||
import { BASE_PATH, SNAPSHOT_STATE, UIM_SNAPSHOT_SHOW_DETAILS_CLICK } from '../../../../constants';
|
||||
import { SNAPSHOT_STATE, UIM_SNAPSHOT_SHOW_DETAILS_CLICK } from '../../../../constants';
|
||||
import { useAppDependencies } from '../../../../index';
|
||||
import { linkToRepository } from '../../../../services/navigation';
|
||||
import { linkToRepository, linkToRestoreSnapshot } from '../../../../services/navigation';
|
||||
import { uiMetricService } from '../../../../services/ui_metric';
|
||||
import { DataPlaceholder, FormattedDateTime, SnapshotDeleteProvider } from '../../../../components';
|
||||
|
||||
|
@ -28,6 +28,7 @@ interface Props {
|
|||
reload: () => Promise<void>;
|
||||
openSnapshotDetailsUrl: (repositoryName: string, snapshotId: string) => string;
|
||||
repositoryFilter?: string;
|
||||
policyFilter?: string;
|
||||
onSnapshotDeleted: (snapshotsDeleted: Array<{ snapshot: string; repository: string }>) => void;
|
||||
}
|
||||
|
||||
|
@ -38,6 +39,7 @@ export const SnapshotTable: React.FunctionComponent<Props> = ({
|
|||
openSnapshotDetailsUrl,
|
||||
onSnapshotDeleted,
|
||||
repositoryFilter,
|
||||
policyFilter,
|
||||
}) => {
|
||||
const {
|
||||
core: { i18n },
|
||||
|
@ -181,7 +183,7 @@ export const SnapshotTable: React.FunctionComponent<Props> = ({
|
|||
iconType="importAction"
|
||||
color="primary"
|
||||
data-test-subj="srsnapshotListRestoreActionButton"
|
||||
href={`#${BASE_PATH}/restore/${repository}/${snapshot}`}
|
||||
href={linkToRestoreSnapshot(repository, snapshot)}
|
||||
isDisabled={!canRestore}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
|
@ -253,6 +255,9 @@ export const SnapshotTable: React.FunctionComponent<Props> = ({
|
|||
repository: {
|
||||
type: 'string',
|
||||
},
|
||||
policyName: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -336,8 +341,15 @@ export const SnapshotTable: React.FunctionComponent<Props> = ({
|
|||
})),
|
||||
},
|
||||
],
|
||||
defaultQuery: repositoryFilter
|
||||
? Query.parse(`repository:'${repositoryFilter}'`, {
|
||||
defaultQuery: policyFilter
|
||||
? Query.parse(`policyName="${policyFilter}"`, {
|
||||
schema: {
|
||||
...searchSchema,
|
||||
strict: true,
|
||||
},
|
||||
})
|
||||
: repositoryFilter
|
||||
? Query.parse(`repository="${repositoryFilter}"`, {
|
||||
schema: {
|
||||
...searchSchema,
|
||||
strict: true,
|
||||
|
@ -359,7 +371,7 @@ export const SnapshotTable: React.FunctionComponent<Props> = ({
|
|||
rowProps={() => ({
|
||||
'data-test-subj': 'row',
|
||||
})}
|
||||
cellProps={(item: any, column: any) => ({
|
||||
cellProps={() => ({
|
||||
'data-test-subj': 'cell',
|
||||
})}
|
||||
data-test-subj="snapshotTable"
|
||||
|
|
|
@ -8,3 +8,4 @@ export * from './app_requests';
|
|||
export * from './repository_requests';
|
||||
export * from './snapshot_requests';
|
||||
export * from './restore_requests';
|
||||
export * from './policy_requests';
|
||||
|
|
|
@ -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 { API_BASE_PATH } from '../../../../common/constants';
|
||||
import { SlmPolicy } from '../../../../common/types';
|
||||
import { httpService } from './http';
|
||||
import { useRequest } from './use_request';
|
||||
|
||||
export const useLoadPolicies = () => {
|
||||
return useRequest({
|
||||
path: httpService.addBasePath(`${API_BASE_PATH}policies`),
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
export const useLoadPolicy = (name: SlmPolicy['name']) => {
|
||||
return useRequest({
|
||||
path: httpService.addBasePath(`${API_BASE_PATH}policy/${encodeURIComponent(name)}`),
|
||||
method: 'get',
|
||||
});
|
||||
};
|
|
@ -5,4 +5,4 @@
|
|||
*/
|
||||
|
||||
export { breadcrumbService } from './breadcrumb';
|
||||
export { linkToRepository, linkToRepositories, linkToSnapshots } from './links';
|
||||
export * from './links';
|
||||
|
|
|
@ -14,9 +14,36 @@ export function linkToRepository(repositoryName: string) {
|
|||
return `#${BASE_PATH}/repositories/${encodeURIComponent(repositoryName)}`;
|
||||
}
|
||||
|
||||
export function linkToSnapshots(repositoryName?: string) {
|
||||
export function linkToEditRepository(repositoryName: string) {
|
||||
return `#${BASE_PATH}/edit_repository/${encodeURIComponent(repositoryName)}`;
|
||||
}
|
||||
|
||||
export function linkToAddRepository() {
|
||||
return `#${BASE_PATH}/add_repository`;
|
||||
}
|
||||
|
||||
export function linkToSnapshots(repositoryName?: string, policyName?: string) {
|
||||
if (repositoryName) {
|
||||
return `#${BASE_PATH}/snapshots?repository=${repositoryName}`;
|
||||
return `#${BASE_PATH}/snapshots?repository=${encodeURIComponent(repositoryName)}`;
|
||||
}
|
||||
if (policyName) {
|
||||
return `#${BASE_PATH}/snapshots?policy=${encodeURIComponent(policyName)}`;
|
||||
}
|
||||
return `#${BASE_PATH}/snapshots`;
|
||||
}
|
||||
|
||||
export function linkToSnapshot(repositoryName: string, snapshotName: string) {
|
||||
return `#${BASE_PATH}/snapshots/${encodeURIComponent(repositoryName)}/${encodeURIComponent(
|
||||
snapshotName
|
||||
)}`;
|
||||
}
|
||||
|
||||
export function linkToRestoreSnapshot(repositoryName: string, snapshotName: string) {
|
||||
return `#${BASE_PATH}/restore/${encodeURIComponent(repositoryName)}/${encodeURIComponent(
|
||||
snapshotName
|
||||
)}`;
|
||||
}
|
||||
|
||||
export function linkToPolicy(policyName: string) {
|
||||
return `#${BASE_PATH}/policies/${encodeURIComponent(policyName)}`;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 elasticsearchJsPlugin = (Client: any, config: any, components: any) => {
|
||||
const ca = components.clientAction.factory;
|
||||
|
||||
Client.prototype.slm = components.clientAction.namespaceFactory();
|
||||
const slm = Client.prototype.slm.prototype;
|
||||
|
||||
slm.policies = ca({
|
||||
urls: [
|
||||
{
|
||||
fmt: '/_slm/policy',
|
||||
},
|
||||
],
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
slm.policy = ca({
|
||||
urls: [
|
||||
{
|
||||
fmt: '/_slm/policy/<%=name%>',
|
||||
req: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
method: 'GET',
|
||||
});
|
||||
};
|
|
@ -9,6 +9,7 @@ export {
|
|||
serializeRepositorySettings,
|
||||
} from './repository_serialization';
|
||||
export { cleanSettings } from './clean_settings';
|
||||
export { deserializeSnapshotDetails } from './snapshot_serialization';
|
||||
export { deserializeSnapshotDetails, deserializeSnapshotConfig } from './snapshot_serialization';
|
||||
export { deserializeRestoreShard } from './restore_serialization';
|
||||
export { getManagedRepositoryName } from './get_managed_repository_name';
|
||||
export { deserializePolicy } from './policy_serialization';
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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 { deserializePolicy } from './policy_serialization';
|
||||
|
||||
describe('repository_serialization', () => {
|
||||
describe('deserializePolicy()', () => {
|
||||
it('should deserialize a new slm policy', () => {
|
||||
expect(
|
||||
deserializePolicy('my-backups-snapshots', {
|
||||
version: 1,
|
||||
modified_date: '2019-07-09T22:11:55.761Z',
|
||||
modified_date_millis: 1562710315761,
|
||||
policy: {
|
||||
name: '<daily-snap-{now/d}>',
|
||||
schedule: '0 30 1 * * ?',
|
||||
repository: 'my-backups',
|
||||
config: {
|
||||
indices: ['kibana-*'],
|
||||
ignore_unavailable: false,
|
||||
include_global_state: false,
|
||||
metadata: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
},
|
||||
next_execution: '2019-07-11T01:30:00.000Z',
|
||||
next_execution_millis: 1562722200000,
|
||||
})
|
||||
).toEqual({
|
||||
name: 'my-backups-snapshots',
|
||||
version: 1,
|
||||
modifiedDate: '2019-07-09T22:11:55.761Z',
|
||||
modifiedDateMillis: 1562710315761,
|
||||
snapshotName: '<daily-snap-{now/d}>',
|
||||
schedule: '0 30 1 * * ?',
|
||||
repository: 'my-backups',
|
||||
config: {
|
||||
indices: ['kibana-*'],
|
||||
includeGlobalState: false,
|
||||
ignoreUnavailable: false,
|
||||
metadata: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
nextExecution: '2019-07-11T01:30:00.000Z',
|
||||
nextExecutionMillis: 1562722200000,
|
||||
});
|
||||
});
|
||||
|
||||
it('should deserialize a slm policy with success and failure info', () => {
|
||||
expect(
|
||||
deserializePolicy('my-backups-snapshots', {
|
||||
version: 1,
|
||||
modified_date: '2019-07-09T22:11:55.761Z',
|
||||
modified_date_millis: 1562710315761,
|
||||
policy: {
|
||||
name: '<daily-snap-{now/d}>',
|
||||
schedule: '0 30 1 * * ?',
|
||||
repository: 'my-backups',
|
||||
config: {
|
||||
indices: ['kibana-*'],
|
||||
ignore_unavailable: false,
|
||||
include_global_state: false,
|
||||
},
|
||||
},
|
||||
next_execution: '2019-07-11T01:30:00.000Z',
|
||||
next_execution_millis: 1562722200000,
|
||||
last_success: {
|
||||
snapshot_name: 'daily-snap-2019.07.10-ya_cajvksbcidtlbnnxt9q',
|
||||
time_string: '2019-07-10T01:30:02.548Z',
|
||||
time: 1562722202548,
|
||||
},
|
||||
last_failure: {
|
||||
snapshot_name: 'daily-snap-2019.07.10-cvi4m0uts5knejcrgq4qxq',
|
||||
time_string: '2019-07-10T01:30:02.443Z',
|
||||
time: 1562722202443,
|
||||
details: `{"type":"concurrent_snapshot_execution_exception",
|
||||
"reason":"[my-backups:daily-snap-2019.07.10-cvi4m0uts5knejcrgq4qxq] a snapshot is already running",
|
||||
"stack_trace":"Some stack trace"}`,
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
name: 'my-backups-snapshots',
|
||||
version: 1,
|
||||
modifiedDate: '2019-07-09T22:11:55.761Z',
|
||||
modifiedDateMillis: 1562710315761,
|
||||
snapshotName: '<daily-snap-{now/d}>',
|
||||
schedule: '0 30 1 * * ?',
|
||||
repository: 'my-backups',
|
||||
config: { indices: ['kibana-*'], includeGlobalState: false, ignoreUnavailable: false },
|
||||
nextExecution: '2019-07-11T01:30:00.000Z',
|
||||
nextExecutionMillis: 1562722200000,
|
||||
lastFailure: {
|
||||
details: {
|
||||
reason:
|
||||
'[my-backups:daily-snap-2019.07.10-cvi4m0uts5knejcrgq4qxq] a snapshot is already running',
|
||||
stack_trace: 'Some stack trace',
|
||||
type: 'concurrent_snapshot_execution_exception',
|
||||
},
|
||||
snapshotName: 'daily-snap-2019.07.10-cvi4m0uts5knejcrgq4qxq',
|
||||
time: 1562722202443,
|
||||
timeString: '2019-07-10T01:30:02.443Z',
|
||||
},
|
||||
lastSuccess: {
|
||||
snapshotName: 'daily-snap-2019.07.10-ya_cajvksbcidtlbnnxt9q',
|
||||
time: 1562722202548,
|
||||
timeString: '2019-07-10T01:30:02.548Z',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 { SlmPolicy, SlmPolicyEs } from '../../common/types';
|
||||
import { deserializeSnapshotConfig } from './';
|
||||
|
||||
export const deserializePolicy = (name: string, esPolicy: SlmPolicyEs): SlmPolicy => {
|
||||
const {
|
||||
version,
|
||||
modified_date: modifiedDate,
|
||||
modified_date_millis: modifiedDateMillis,
|
||||
policy: { name: snapshotName, schedule, repository, config },
|
||||
next_execution: nextExecution,
|
||||
next_execution_millis: nextExecutionMillis,
|
||||
last_failure: lastFailure,
|
||||
last_success: lastSuccess,
|
||||
} = esPolicy;
|
||||
|
||||
const policy: SlmPolicy = {
|
||||
name,
|
||||
version,
|
||||
modifiedDate,
|
||||
modifiedDateMillis,
|
||||
snapshotName,
|
||||
schedule,
|
||||
repository,
|
||||
config: deserializeSnapshotConfig(config),
|
||||
nextExecution,
|
||||
nextExecutionMillis,
|
||||
};
|
||||
|
||||
if (lastFailure) {
|
||||
const {
|
||||
snapshot_name: failureSnapshotName,
|
||||
time: failureTime,
|
||||
time_string: failureTimeString,
|
||||
details: failureDetails,
|
||||
} = lastFailure;
|
||||
|
||||
let jsonFailureDetails;
|
||||
|
||||
try {
|
||||
jsonFailureDetails = JSON.parse(failureDetails);
|
||||
} catch (e) {
|
||||
// silently swallow json parsing error
|
||||
// we don't expect ES to return unparsable json
|
||||
}
|
||||
|
||||
policy.lastFailure = {
|
||||
snapshotName: failureSnapshotName,
|
||||
time: failureTime,
|
||||
timeString: failureTimeString,
|
||||
details: jsonFailureDetails || failureDetails,
|
||||
};
|
||||
}
|
||||
|
||||
if (lastSuccess) {
|
||||
const {
|
||||
snapshot_name: successSnapshotName,
|
||||
time: successTime,
|
||||
time_string: successTimeString,
|
||||
} = lastSuccess;
|
||||
|
||||
policy.lastSuccess = {
|
||||
snapshotName: successSnapshotName,
|
||||
time: successTime,
|
||||
timeString: successTimeString,
|
||||
};
|
||||
}
|
||||
|
||||
return policy;
|
||||
};
|
|
@ -6,8 +6,12 @@
|
|||
|
||||
import { sortBy } from 'lodash';
|
||||
|
||||
import { SnapshotDetails } from '../../common/types';
|
||||
import { SnapshotDetailsEs } from '../types';
|
||||
import {
|
||||
SnapshotDetails,
|
||||
SnapshotDetailsEs,
|
||||
SnapshotConfig,
|
||||
SnapshotConfigEs,
|
||||
} from '../../common/types';
|
||||
|
||||
export function deserializeSnapshotDetails(
|
||||
repository: string,
|
||||
|
@ -33,6 +37,7 @@ export function deserializeSnapshotDetails(
|
|||
duration_in_millis: durationInMillis,
|
||||
failures = [],
|
||||
shards,
|
||||
metadata: { policy: policyName } = { policy: undefined },
|
||||
} = snapshotDetailsEs;
|
||||
|
||||
// If an index has multiple failures, we'll want to see them grouped together.
|
||||
|
@ -60,7 +65,7 @@ export function deserializeSnapshotDetails(
|
|||
// Sort by index name.
|
||||
const indexFailures = sortBy(Object.values(indexToFailuresMap), ({ index }) => index);
|
||||
|
||||
return {
|
||||
const snapshotDetails: SnapshotDetails = {
|
||||
repository,
|
||||
snapshot,
|
||||
uuid,
|
||||
|
@ -78,4 +83,34 @@ export function deserializeSnapshotDetails(
|
|||
shards,
|
||||
isManagedRepository: repository === managedRepository,
|
||||
};
|
||||
|
||||
if (policyName) {
|
||||
snapshotDetails.policyName = policyName;
|
||||
}
|
||||
return snapshotDetails;
|
||||
}
|
||||
|
||||
export function deserializeSnapshotConfig(snapshotConfigEs: SnapshotConfigEs): SnapshotConfig {
|
||||
const {
|
||||
indices,
|
||||
ignore_unavailable: ignoreUnavailable,
|
||||
include_global_state: includeGlobalState,
|
||||
partial,
|
||||
metadata,
|
||||
} = snapshotConfigEs;
|
||||
|
||||
const snapshotConfig: SnapshotConfig = {
|
||||
indices,
|
||||
ignoreUnavailable,
|
||||
includeGlobalState,
|
||||
partial,
|
||||
metadata,
|
||||
};
|
||||
|
||||
return Object.entries(snapshotConfig).reduce((config: any, [key, value]) => {
|
||||
if (value !== undefined) {
|
||||
config[key] = value;
|
||||
}
|
||||
return config;
|
||||
}, {});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* 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 { Request, ResponseToolkit } from 'hapi';
|
||||
import { getAllHandler, getOneHandler } from './policy';
|
||||
|
||||
describe('[Snapshot and Restore API Routes] Restore', () => {
|
||||
const mockRequest = {} as Request;
|
||||
const mockResponseToolkit = {} as ResponseToolkit;
|
||||
const mockEsPolicy = {
|
||||
version: 1,
|
||||
modified_date_millis: 1562710315761,
|
||||
policy: {
|
||||
name: '<daily-snap-{now/d}>',
|
||||
schedule: '0 30 1 * * ?',
|
||||
repository: 'my-backups',
|
||||
config: {},
|
||||
},
|
||||
next_execution_millis: 1562722200000,
|
||||
};
|
||||
const mockPolicy = {
|
||||
version: 1,
|
||||
modifiedDateMillis: 1562710315761,
|
||||
snapshotName: '<daily-snap-{now/d}>',
|
||||
schedule: '0 30 1 * * ?',
|
||||
repository: 'my-backups',
|
||||
config: {},
|
||||
nextExecutionMillis: 1562722200000,
|
||||
};
|
||||
|
||||
describe('getAllHandler()', () => {
|
||||
it('should arrify policies returned from ES', async () => {
|
||||
const mockEsResponse = {
|
||||
fooPolicy: mockEsPolicy,
|
||||
barPolicy: mockEsPolicy,
|
||||
};
|
||||
const callWithRequest = jest.fn().mockReturnValueOnce(mockEsResponse);
|
||||
const expectedResponse = {
|
||||
policies: [
|
||||
{
|
||||
name: 'fooPolicy',
|
||||
...mockPolicy,
|
||||
},
|
||||
{
|
||||
name: 'barPolicy',
|
||||
...mockPolicy,
|
||||
},
|
||||
],
|
||||
};
|
||||
await expect(
|
||||
getAllHandler(mockRequest, callWithRequest, mockResponseToolkit)
|
||||
).resolves.toEqual(expectedResponse);
|
||||
});
|
||||
|
||||
it('should return empty array if no repositories returned from ES', async () => {
|
||||
const mockEsResponse = {};
|
||||
const callWithRequest = jest.fn().mockReturnValueOnce(mockEsResponse);
|
||||
const expectedResponse = { policies: [] };
|
||||
await expect(
|
||||
getAllHandler(mockRequest, callWithRequest, mockResponseToolkit)
|
||||
).resolves.toEqual(expectedResponse);
|
||||
});
|
||||
|
||||
it('should throw if ES error', async () => {
|
||||
const callWithRequest = jest.fn().mockRejectedValueOnce(new Error());
|
||||
await expect(
|
||||
getAllHandler(mockRequest, callWithRequest, mockResponseToolkit)
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOneHandler()', () => {
|
||||
const name = 'fooPolicy';
|
||||
const mockOneRequest = ({
|
||||
params: {
|
||||
name,
|
||||
},
|
||||
} as unknown) as Request;
|
||||
|
||||
it('should return policy if returned from ES', async () => {
|
||||
const mockEsResponse = {
|
||||
[name]: mockEsPolicy,
|
||||
};
|
||||
const callWithRequest = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(mockEsResponse)
|
||||
.mockResolvedValueOnce({});
|
||||
const expectedResponse = {
|
||||
policy: {
|
||||
name,
|
||||
...mockPolicy,
|
||||
},
|
||||
};
|
||||
await expect(
|
||||
getOneHandler(mockOneRequest, callWithRequest, mockResponseToolkit)
|
||||
).resolves.toEqual(expectedResponse);
|
||||
});
|
||||
|
||||
it('should return 404 error if not returned from ES', async () => {
|
||||
const mockEsResponse = {};
|
||||
const callWithRequest = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(mockEsResponse)
|
||||
.mockResolvedValueOnce({});
|
||||
await expect(
|
||||
getOneHandler(mockRequest, callWithRequest, mockResponseToolkit)
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should throw if ES error', async () => {
|
||||
const callWithRequest = jest.fn().mockRejectedValueOnce(new Error());
|
||||
await expect(
|
||||
getOneHandler(mockOneRequest, callWithRequest, mockResponseToolkit)
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { Router, RouterRouteHandler } from '../../../../../server/lib/create_router';
|
||||
import { wrapCustomError } from '../../../../../server/lib/create_router/error_wrappers';
|
||||
import { SlmPolicyEs, SlmPolicy } from '../../../common/types';
|
||||
import { deserializePolicy } from '../../lib';
|
||||
|
||||
export function registerPolicyRoutes(router: Router) {
|
||||
router.get('policies', getAllHandler);
|
||||
router.get('policy/{name}', getOneHandler);
|
||||
}
|
||||
|
||||
export const getAllHandler: RouterRouteHandler = async (
|
||||
req,
|
||||
callWithRequest
|
||||
): Promise<{
|
||||
policies: SlmPolicy[];
|
||||
}> => {
|
||||
// Get policies
|
||||
const policiesByName: {
|
||||
[key: string]: SlmPolicyEs;
|
||||
} = await callWithRequest('slm.policies', {
|
||||
human: true,
|
||||
});
|
||||
|
||||
// Deserialize policies
|
||||
return {
|
||||
policies: Object.entries(policiesByName).map(([name, policy]) =>
|
||||
deserializePolicy(name, policy)
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const getOneHandler: RouterRouteHandler = async (
|
||||
req,
|
||||
callWithRequest
|
||||
): Promise<{
|
||||
policy: SlmPolicy;
|
||||
}> => {
|
||||
// Get policy
|
||||
const { name } = req.params;
|
||||
const policiesByName: {
|
||||
[key: string]: SlmPolicyEs;
|
||||
} = await callWithRequest('slm.policy', {
|
||||
name,
|
||||
human: true,
|
||||
});
|
||||
|
||||
if (!policiesByName[name]) {
|
||||
// If policy doesn't exist, ES will return 200 with an empty object, so manually throw 404 here
|
||||
throw wrapCustomError(new Error('Policy not found'), 404);
|
||||
}
|
||||
|
||||
// Deserialize policy
|
||||
return {
|
||||
policy: deserializePolicy(name, policiesByName[name]),
|
||||
};
|
||||
};
|
|
@ -9,10 +9,12 @@ import { registerAppRoutes } from './app';
|
|||
import { registerRepositoriesRoutes } from './repositories';
|
||||
import { registerSnapshotsRoutes } from './snapshots';
|
||||
import { registerRestoreRoutes } from './restore';
|
||||
import { registerPolicyRoutes } from './policy';
|
||||
|
||||
export const registerRoutes = (router: Router, plugins: Plugins): void => {
|
||||
registerAppRoutes(router, plugins);
|
||||
registerRepositoriesRoutes(router, plugins);
|
||||
registerSnapshotsRoutes(router, plugins);
|
||||
registerRestoreRoutes(router);
|
||||
registerPolicyRoutes(router);
|
||||
};
|
||||
|
|
|
@ -5,10 +5,9 @@
|
|||
*/
|
||||
import { Router, RouterRouteHandler } from '../../../../../server/lib/create_router';
|
||||
import { wrapEsError } from '../../../../../server/lib/create_router/error_wrappers';
|
||||
import { SnapshotDetails } from '../../../common/types';
|
||||
import { SnapshotDetails, SnapshotDetailsEs } from '../../../common/types';
|
||||
import { Plugins } from '../../../shim';
|
||||
import { deserializeSnapshotDetails, getManagedRepositoryName } from '../../lib';
|
||||
import { SnapshotDetailsEs } from '../../types';
|
||||
|
||||
let callWithInternalUser: any;
|
||||
|
||||
|
|
|
@ -1,30 +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.
|
||||
*/
|
||||
|
||||
export interface SnapshotDetailsEs {
|
||||
snapshot: string;
|
||||
uuid: string;
|
||||
version_id: number;
|
||||
version: string;
|
||||
indices: string[];
|
||||
include_global_state: boolean;
|
||||
state: string;
|
||||
/** e.g. '2019-04-05T21:56:40.438Z' */
|
||||
start_time: string;
|
||||
start_time_in_millis: number;
|
||||
/** e.g. '2019-04-05T21:56:45.210Z' */
|
||||
end_time: string;
|
||||
end_time_in_millis: number;
|
||||
duration_in_millis: number;
|
||||
failures: any[];
|
||||
shards: SnapshotDetailsShardsStatusEs;
|
||||
}
|
||||
|
||||
interface SnapshotDetailsShardsStatusEs {
|
||||
total: number;
|
||||
failed: number;
|
||||
successful: number;
|
||||
}
|
|
@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { Legacy } from 'kibana';
|
||||
import { createRouter, Router } from '../../server/lib/create_router';
|
||||
import { registerLicenseChecker } from '../../server/lib/register_license_checker';
|
||||
import { elasticsearchJsPlugin } from './server/client/elasticsearch_slm';
|
||||
|
||||
export interface Core {
|
||||
http: {
|
||||
|
@ -39,7 +40,10 @@ export function createShim(
|
|||
return {
|
||||
core: {
|
||||
http: {
|
||||
createRouter: (basePath: string) => createRouter(server, pluginId, basePath),
|
||||
createRouter: (basePath: string) =>
|
||||
createRouter(server, pluginId, basePath, {
|
||||
plugins: [elasticsearchJsPlugin],
|
||||
}),
|
||||
},
|
||||
i18n,
|
||||
},
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const callWithRequestFactory = (server, request) => {
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
|
||||
return (...args) => {
|
||||
return callWithRequest(request, ...args);
|
||||
};
|
||||
export const callWithRequestFactory = (server, pluginId, config) => {
|
||||
const { callWithRequest } = config
|
||||
? server.plugins.elasticsearch.createCluster(pluginId, config)
|
||||
: server.plugins.elasticsearch.getCluster('data');
|
||||
return callWithRequest;
|
||||
};
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Request } from 'hapi';
|
||||
import { Legacy } from 'kibana';
|
||||
import { CallCluster } from 'src/legacy/core_plugins/elasticsearch';
|
||||
|
||||
|
@ -12,5 +11,8 @@ export type CallWithRequest = (...args: any[]) => CallCluster;
|
|||
|
||||
export declare function callWithRequestFactory(
|
||||
server: Legacy.Server,
|
||||
request: Request
|
||||
pluginId: string,
|
||||
config?: {
|
||||
plugins: any[];
|
||||
}
|
||||
): CallWithRequest;
|
||||
|
|
|
@ -29,7 +29,10 @@ export interface Router {
|
|||
export declare function createRouter(
|
||||
server: Legacy.Server,
|
||||
pluginId: string,
|
||||
apiBasePath: string
|
||||
apiBasePath: string,
|
||||
config?: {
|
||||
plugins: any[];
|
||||
}
|
||||
): Router;
|
||||
|
||||
export declare function isEsErrorFactory(server: Legacy.Server): any;
|
||||
|
|
|
@ -16,16 +16,20 @@ export const isEsErrorFactory = server => {
|
|||
return createIsEsError(server);
|
||||
};
|
||||
|
||||
export const createRouter = (server, pluginId, apiBasePath = '') => {
|
||||
export const createRouter = (server, pluginId, apiBasePath = '', config) => {
|
||||
const isEsError = isEsErrorFactory(server);
|
||||
|
||||
// NOTE: The license-checking logic depends on the xpack_main plugin, so if your plugin
|
||||
// consumes this helper, make sure it declares 'xpack_main' as a dependency.
|
||||
const licensePreRouting = licensePreRoutingFactory(server, pluginId);
|
||||
|
||||
const callWithRequestInstance = callWithRequestFactory(server, pluginId, config);
|
||||
|
||||
const requestHandler = (handler) => async (request, h) => {
|
||||
const callWithRequest = callWithRequestFactory(server, request);
|
||||
try {
|
||||
const callWithRequest = (...args) => {
|
||||
return callWithRequestInstance(request, ...args);
|
||||
};
|
||||
return await handler(request, callWithRequest, h);
|
||||
} catch (err) {
|
||||
if (err instanceof Boom) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue