diff --git a/models/interfaces.ts b/models/interfaces.ts index 3ca383060..136a900a2 100644 --- a/models/interfaces.ts +++ b/models/interfaces.ts @@ -144,7 +144,8 @@ export interface SMSnapshotConfig { ignore_unavailable?: boolean; include_global_state?: boolean; partial?: boolean; - date_expression?: string; + date_format?: string; + date_format_timezone?: string; } export interface SMCreation { diff --git a/public/index_management_app.tsx b/public/index_management_app.tsx index 35c6ce38e..a7f684c43 100644 --- a/public/index_management_app.tsx +++ b/public/index_management_app.tsx @@ -22,7 +22,7 @@ import Main from "./pages/Main"; import { CoreServicesContext } from "./components/core_services"; import "./app.scss"; -export function renderApp(coreStart: CoreStart, params: AppMountParameters) { +export function renderApp(coreStart: CoreStart, params: AppMountParameters, landingPage: string) { const http = coreStart.http; const indexService = new IndexService(http); @@ -51,7 +51,7 @@ export function renderApp(coreStart: CoreStart, params: AppMountParameters) { -
+
diff --git a/public/pages/CreateSnapshotPolicy/components/CronSchedule/CronSchedule.tsx b/public/pages/CreateSnapshotPolicy/components/CronSchedule/CronSchedule.tsx index 327fed0da..4843e845f 100644 --- a/public/pages/CreateSnapshotPolicy/components/CronSchedule/CronSchedule.tsx +++ b/public/pages/CreateSnapshotPolicy/components/CronSchedule/CronSchedule.tsx @@ -34,6 +34,7 @@ interface CronScheduleProps { timezone?: string; onChangeTimezone?: (timezone: string) => void; timezoneError?: string; + frequencyTitle?: string; } const CronSchedule = ({ @@ -45,6 +46,7 @@ const CronSchedule = ({ timezone, onChangeTimezone, timezoneError, + frequencyTitle = "Schedule frequency", }: CronScheduleProps) => { const { minute: initMin, hour: initHour, dayOfWeek: initWeek, dayOfMonth: initMonth } = parseCronExpression(cronExpression); @@ -152,7 +154,7 @@ const CronSchedule = ({ return ( <> - + @@ -161,13 +163,15 @@ const CronSchedule = ({ {frequencyType === "custom" ? ( <> - - { - onCronExpressionChange(e.target.value); - }} - /> + + + { + onCronExpressionChange(e.target.value); + }} + /> + ) : ( <> diff --git a/public/pages/CreateSnapshotPolicy/components/Notification/Notification.tsx b/public/pages/CreateSnapshotPolicy/components/Notification/Notification.tsx new file mode 100644 index 000000000..f0ea456aa --- /dev/null +++ b/public/pages/CreateSnapshotPolicy/components/Notification/Notification.tsx @@ -0,0 +1,59 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { ChangeEvent } from "react"; +import { EuiFormRow, EuiSelect, EuiButton, EuiFlexGroup, EuiFlexItem } from "@elastic/eui"; +import "brace/theme/github"; +import "brace/mode/json"; +import { FeatureChannelList } from "../../../../../server/models/interfaces"; +import CustomLabel from "../../../../components/CustomLabel"; + +interface NotificationProps { + channelId: string; + channels: FeatureChannelList[]; + loadingChannels: boolean; + onChangeChannelId: (value: ChangeEvent) => void; + getChannels: () => void; +} + +const Notification = ({ channelId, channels, loadingChannels, onChangeChannelId, getChannels }: NotificationProps) => { + return ( + <> + + + + + ({ value: channel.config_id, text: channel.name }))} + value={channelId} + onChange={onChangeChannelId} + data-test-subj="create-policy-notification-channel-id" + /> + + + + + + + + Manage channels + + + + + ); +}; + +export default Notification; diff --git a/public/pages/CreateSnapshotPolicy/components/Notification/index.ts b/public/pages/CreateSnapshotPolicy/components/Notification/index.ts new file mode 100644 index 000000000..e53a4f342 --- /dev/null +++ b/public/pages/CreateSnapshotPolicy/components/Notification/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import Notification from "./Notification"; + +export default Notification; diff --git a/public/pages/CreateSnapshotPolicy/constants.ts b/public/pages/CreateSnapshotPolicy/constants.ts index c808ad004..e15cf1b21 100644 --- a/public/pages/CreateSnapshotPolicy/constants.ts +++ b/public/pages/CreateSnapshotPolicy/constants.ts @@ -16,7 +16,7 @@ export const getDefaultSMPolicy = (): SMPolicy => ({ schedule: { cron: { expression: "0 20 * * *", - timezone: "America/Los_Angeles", + timezone: "UTC", }, }, }, @@ -34,10 +34,13 @@ export const maxAgeUnitOptions = [ { value: "h", text: "Hours" }, ]; -export const DEFAULT_INDEX_OPTIONS = [{ label: "*" }, { label: "-.opendistro_security" }]; +export const DEFAULT_INDEX_OPTIONS = [{ label: "*" }]; export const ERROR_PROMPT = { NAME: "Name must be provided.", REPO: "Repository must be provided.", TIMEZONE: "Time zone must be provided.", }; + +export const DEFAULT_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; +export const DEFAULT_DATE_FORMAT_TIMEZONE = "UTC"; diff --git a/public/pages/CreateSnapshotPolicy/containers/CreateSnapshotPolicy/CreateSnapshotPolicy.tsx b/public/pages/CreateSnapshotPolicy/containers/CreateSnapshotPolicy/CreateSnapshotPolicy.tsx index fba981fdb..7dd3f07e0 100644 --- a/public/pages/CreateSnapshotPolicy/containers/CreateSnapshotPolicy/CreateSnapshotPolicy.tsx +++ b/public/pages/CreateSnapshotPolicy/containers/CreateSnapshotPolicy/CreateSnapshotPolicy.tsx @@ -26,9 +26,7 @@ import { EuiHorizontalRule, EuiButtonIcon, EuiLink, - EuiComboBox, } from "@elastic/eui"; -import moment from "moment-timezone"; import React, { ChangeEvent, Component } from "react"; import { RouteComponentProps } from "react-router-dom"; import { CoreServicesContext } from "../../../../components/core_services"; @@ -38,22 +36,22 @@ import { BREADCRUMBS, ROUTES, SNAPSHOT_MANAGEMENT_DOCUMENTATION_URL } from "../. import { ContentPanel } from "../../../../components/ContentPanel"; import { IndexService, NotificationService, SnapshotManagementService } from "../../../../services"; import { getErrorMessage, wildcardOption } from "../../../../utils/helpers"; -import SnapshotIndicesRepoInput from "../../components/SnapshotIndicesRepoInput/SnapshotIndicesRepoInput"; -import CronSchedule from "../../components/CronSchedule/CronSchedule"; -import SnapshotAdvancedSettings from "../../components/SnapshotAdvancedSettings/SnapshotAdvancedSettings"; import CustomLabel from "../../../../components/CustomLabel"; -import ChannelNotification from "../../../VisualCreatePolicy/components/ChannelNotification"; import { DEFAULT_INDEX_OPTIONS, ERROR_PROMPT, getDefaultSMPolicy, maxAgeUnitOptions as MAX_AGE_UNIT_OPTIONS } from "../../constants"; import { getIncludeGlobalState, getIgnoreUnavailabel, getAllowPartial, + showNotification, getNotifyCreation, getNotifyDeletion, getNotifyFailure, } from "../helper"; import { parseCronExpression } from "../../components/CronSchedule/helper"; -import { TIMEZONES } from "../../components/CronSchedule/constants"; +import SnapshotIndicesRepoInput from "../../components/SnapshotIndicesRepoInput"; +import CronSchedule from "../../components/CronSchedule"; +import SnapshotAdvancedSettings from "../../components/SnapshotAdvancedSettings"; +import Notification from "../../components/Notification"; interface CreateSMPolicyProps extends RouteComponentProps { snapshotManagementService: SnapshotManagementService; @@ -86,7 +84,7 @@ interface CreateSMPolicyState { deletionScheduleFrequencyType: string; deleteConditionEnabled: boolean; - deletionScheduleEnabled: boolean; + deletionScheduleEnabled: boolean; // whether to use the same schedule as creation advancedSettingsOpen: boolean; @@ -164,6 +162,7 @@ export default class CreateSnapshotPolicy extends Component => { @@ -172,8 +171,7 @@ export default class CreateSnapshotPolicy extends Component ({ label })); const selectedRepoValue = _.get(policy, "snapshot_config.repository", ""); - // TODO SM parse frequency type const { frequencyType: creationScheduleFrequencyType } = parseCronExpression(_.get(policy, "creation.schedule.cron.expression")); const { frequencyType: deletionScheduleFrequencyType } = parseCronExpression(_.get(policy, "deletion.schedule.cron.expression")); + let deleteConditionEnabled = false; + let deletionScheduleEnabled = false; + if (!!_.get(policy, "deletion")) { + deleteConditionEnabled = true; + const creationScheduleExpression = _.get(policy, "creation.schedule.cron.expression"); + const deletionScheduleExpression = _.get(policy, "deletion.schedule.cron.expression"); + if (creationScheduleExpression !== deletionScheduleExpression) deletionScheduleEnabled = true; + } + + const maxAge = policy.deletion?.condition?.max_age; + let maxAgeNum = 1; + let maxAgeUnit = "d"; + if (maxAge) { + maxAgeNum = parseInt(maxAge.substring(0, maxAge.length - 1)); + maxAgeUnit = maxAge[maxAge.length - 1]; + } + this.setState({ policy, policyId: response.response.id, @@ -195,6 +209,10 @@ export default class CreateSnapshotPolicy extends Component { @@ -355,6 +373,10 @@ export default class CreateSnapshotPolicy extends Component => { - console.log(`sm dev get channels`); this.setState({ loadingChannels: true }); try { const { notificationService } = this.props; @@ -417,7 +438,7 @@ export default class CreateSnapshotPolicy extends Component ); - const notifyOnCreation = getNotifyCreation(policy); - const notifyOnDeletion = getNotifyDeletion(policy); - const notifyOnFailure = getNotifyFailure(policy); - - let showNotificationChannel = false; - if (notifyOnCreation || notifyOnDeletion || notifyOnFailure) { - showNotificationChannel = true; - } + const showNotificationChannel = showNotification(policy); return (
@@ -535,9 +549,15 @@ export default class CreateSnapshotPolicy extends Component { - this.setState({ creationScheduleFrequencyType: e.target.value }); + const frequencyType = e.target.value; + let maxAgeUnitToChange = maxAgeUnit; + if (frequencyType == "hourly" && !deleteConditionEnabled) { + maxAgeUnitToChange = "h"; + } + this.setState({ creationScheduleFrequencyType: e.target.value, maxAgeUnit: maxAgeUnitToChange }); }} showTimezone={true} timezone={_.get(policy, "creation.schedule.cron.timezone")} @@ -598,7 +618,11 @@ export default class CreateSnapshotPolicy extends Component - + @@ -611,23 +635,25 @@ export default class CreateSnapshotPolicy extends Component - Deletion schedule + Deletion frequency - Delete snapshots that are outside the retention period + Configure when to check retention conditions and delete snapshots. { this.setState({ deletionScheduleEnabled: !deletionScheduleEnabled }); }} /> + {deletionScheduleEnabled ? ( { this.setState({ deletionScheduleFrequencyType: e.target.value }); @@ -639,7 +665,7 @@ export default class CreateSnapshotPolicy extends Component ) : null} - {" "} + ) : null} @@ -652,9 +678,9 @@ export default class CreateSnapshotPolicy extends Component ) => { this.setState({ policy: this.setPolicyHelper("notification.conditions.creation", e.target.checked) }); }} @@ -663,9 +689,9 @@ export default class CreateSnapshotPolicy extends Component ) => { this.setState({ policy: this.setPolicyHelper("notification.conditions.deletion", e.target.checked) }); }} @@ -674,17 +700,17 @@ export default class CreateSnapshotPolicy extends Component ) => { this.setState({ policy: this.setPolicyHelper("notification.conditions.failure", e.target.checked) }); }} />
{showNotificationChannel ? ( - -
+ {/* TODO SM Haven't fininalized the design for this before 2.1 release */} + {/*

Snapshot naming settings

@@ -740,10 +767,13 @@ export default class CreateSnapshotPolicy extends Component { - this.setState({ policy: this.setPolicyHelper("snapshot_config.date_format", e.target.value) }); + let dateFormat = e.target.value; + if (!dateFormat) { + dateFormat = DEFAULT_DATE_FORMAT; + } + this.setState({ dateFormat }); }} /> @@ -755,13 +785,14 @@ export default class CreateSnapshotPolicy extends Component `${tz} (${moment.tz(tz).format("Z")})`} - selectedOptions={[{ label: _.get(policy, "snapshot_config.date_format_timezone") ?? "" }]} + selectedOptions={[{ label: _.get(policy, "snapshot_config.date_format_timezone") ?? DEFAULT_DATE_FORMAT_TIMEZONE }]} onChange={(options) => { - const timezone = _.first(options)?.label; + let timezone = _.first(options)?.label; + if (!timezone) timezone = DEFAULT_DATE_FORMAT_TIMEZONE; this.setState({ policy: this.setPolicyHelper("snapshot_config.date_format_timezone", timezone) }); }} /> -
+
*/} )} @@ -783,16 +814,6 @@ export default class CreateSnapshotPolicy extends Component { - const maxAge = policy.deletion?.condition?.max_age; - if (maxAge) { - this.setState({ - maxAgeNum: parseInt(maxAge.substring(0, maxAge.length - 1)), - maxAgeUnit: maxAge[maxAge.length - 1], - }); - } - }; - onChangeMaxCount = (e: ChangeEvent) => { // Received NaN for the `value` attribute. If this is expected, cast the value to a string. const maxCount = isNaN(parseInt(e.target.value)) ? undefined : parseInt(e.target.value); diff --git a/public/pages/CreateSnapshotPolicy/containers/helper.ts b/public/pages/CreateSnapshotPolicy/containers/helper.ts index af0f7650c..a68ec0d4c 100644 --- a/public/pages/CreateSnapshotPolicy/containers/helper.ts +++ b/public/pages/CreateSnapshotPolicy/containers/helper.ts @@ -28,3 +28,16 @@ export const getNotifyDeletion = (policy: SMPolicy) => { export const getNotifyFailure = (policy: SMPolicy) => { return String(_.get(policy, "notification.conditions.failure", false)) == "true"; }; + +export const showNotification = (policy: SMPolicy) => { + const notifyOnCreation = getNotifyCreation(policy); + const notifyOnDeletion = getNotifyDeletion(policy); + const notifyOnFailure = getNotifyFailure(policy); + + let showNotificationChannel = false; + if (notifyOnCreation || notifyOnDeletion || notifyOnFailure) { + showNotificationChannel = true; + } + + return showNotificationChannel; +}; diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx index d8a111ee7..caa574bab 100644 --- a/public/pages/Main/Main.tsx +++ b/public/pages/Main/Main.tsx @@ -74,7 +74,9 @@ const HIDDEN_NAV_ROUTES = [ ROUTES.EDIT_SNAPSHOT_POLICY, ]; -interface MainProps extends RouteComponentProps {} +interface MainProps extends RouteComponentProps { + landingPage: string; +} export default class Main extends Component { render() { @@ -145,6 +147,9 @@ export default class Main extends Component { ], }, ]; + + const { landingPage } = this.props; + return ( {(core: CoreStart | null) => @@ -188,7 +193,11 @@ export default class Main extends Component { ( - + )} /> { )} /> - + diff --git a/public/pages/Repositories/components/CreateRepositoryFlyout/CreateRepositoryFlyout.tsx b/public/pages/Repositories/components/CreateRepositoryFlyout/CreateRepositoryFlyout.tsx index bdc680295..686c0c015 100644 --- a/public/pages/Repositories/components/CreateRepositoryFlyout/CreateRepositoryFlyout.tsx +++ b/public/pages/Repositories/components/CreateRepositoryFlyout/CreateRepositoryFlyout.tsx @@ -5,6 +5,7 @@ import { EuiAccordion, + EuiCallOut, EuiCodeEditor, EuiComboBox, EuiComboBoxOptionOption, @@ -153,6 +154,16 @@ export default class CreateRepositoryFlyout extends Component +

+ Define a repository by custom type and settings.{" "} + + View sample configurations + +

+ + ); let configuration; if (selectedRepoTypeOption == "fs") { configuration = ( @@ -197,14 +208,20 @@ export default class CreateRepositoryFlyout extends Component - -

- Define a repository by custom type and settings.{" "} - - View sample configurations + +

+ To use a custom repository, such as Amazon S3, Azure Blob Storage or similar, install and configure the respective repository + plugin on OpenSearch and then define the repository configuration below.{" "} + + Learn more

-
+ + + + + + { - const showSymbol = _.truncate(name, { length: 20 }); + const showSymbol = _.truncate(name, { length: 60 }); return ( this.props.history.push(`${ROUTES.SNAPSHOT_POLICY_DETAILS}?id=${name}`)}> {showSymbol} @@ -127,7 +126,7 @@ export default class SnapshotPolicies extends Component { - return truncateSpan(value); + return truncateSpan(value, 60); }, }, { @@ -135,6 +134,7 @@ export default class SnapshotPolicies extends Component { const expression = name; const timezone = item.creation.schedule.cron.timezone; @@ -146,6 +146,7 @@ export default class SnapshotPolicies extends Component { return truncateSpan(value); }, @@ -287,6 +289,7 @@ export default class SnapshotPolicies extends Component { @@ -305,6 +308,7 @@ export default class SnapshotPolicies extends Component { diff --git a/public/pages/SnapshotPolicyDetails/containers/SnapshotPolicyDetails/SnapshotPolicyDetails.tsx b/public/pages/SnapshotPolicyDetails/containers/SnapshotPolicyDetails/SnapshotPolicyDetails.tsx index 53f667840..4c647f858 100644 --- a/public/pages/SnapshotPolicyDetails/containers/SnapshotPolicyDetails/SnapshotPolicyDetails.tsx +++ b/public/pages/SnapshotPolicyDetails/containers/SnapshotPolicyDetails/SnapshotPolicyDetails.tsx @@ -21,7 +21,7 @@ import { EuiText, EuiTitle, } from "@elastic/eui"; -import { SnapshotManagementService } from "../../../../services"; +import { NotificationService, SnapshotManagementService } from "../../../../services"; import { SMMetadata, SMPolicy } from "../../../../../models/interfaces"; import { CoreServicesContext } from "../../../../components/core_services"; import { BREADCRUMBS, ROUTES } from "../../../../utils/constants"; @@ -35,9 +35,11 @@ import { ModalConsumer } from "../../../../components/Modal"; import InfoModal from "../../components/InfoModal"; import { getAllowPartial, getIgnoreUnavailabel, getIncludeGlobalState } from "../../../CreateSnapshotPolicy/containers/helper"; import { truncateSpan } from "../../../Snapshots/helper"; +import { NotificationConfig } from "../../../../../server/models/interfaces"; interface SnapshotPolicyDetailsProps extends RouteComponentProps { snapshotManagementService: SnapshotManagementService; + notificationService: NotificationService; } interface SnapshotPolicyDetailsState { @@ -47,6 +49,8 @@ interface SnapshotPolicyDetailsState { metadata: SMMetadata | null; isDeleteModalVisible: boolean; + + channel: NotificationConfig | null; } export default class SnapshotPolicyDetails extends Component { @@ -61,6 +65,7 @@ export default class SnapshotPolicyDetails extends Component => { + try { + const { notificationService } = this.props; + const response = await notificationService.getChannel(channelId); + + if (response.ok) { + const configList = response.response.config_list; + let channel = null; + if (configList.length == 1) channel = configList[0]; + this.setState({ channel }); + } else { + this.context.notifications.toasts.addDanger(`Could not load notification channel: ${response.error}`); + } + } catch (err) { + this.context.notifications.toasts.addDanger(getErrorMessage(err, "Could not load the notification channel")); + } + }; + onEdit = () => { const { policyId } = this.state; if (policyId) { @@ -188,7 +216,7 @@ export default class SnapshotPolicyDetails extends Component notiConditions[key]) .join(", "); } - console.log(`sm dev notification ${notiActivities}`); + + let channelDetail =

None

; + if (!!channel?.config.name) { + channelDetail = ( + + {channel?.config.name} ({channel?.config.config_type}) + + ); + } const notificationItems = [ { term: "Notify on snapshot activities", value: notiActivities }, - { term: "Channels", value: _.get(policy, "notification.channel.id") }, + { term: "Channel", value: channelDetail }, ]; let creationLatestActivity: LatestActivities = { activityType: "Creation" }; diff --git a/public/pages/Snapshots/components/CreateSnapshotFlyout/CreateSnapshotFlyout.tsx b/public/pages/Snapshots/components/CreateSnapshotFlyout/CreateSnapshotFlyout.tsx index 54a1d12ba..2ab6397d9 100644 --- a/public/pages/Snapshots/components/CreateSnapshotFlyout/CreateSnapshotFlyout.tsx +++ b/public/pages/Snapshots/components/CreateSnapshotFlyout/CreateSnapshotFlyout.tsx @@ -87,7 +87,6 @@ export default class CreateSnapshotFlyout extends Component sortable: true, dataType: "string", render: (name: string, item: SnapshotsWithRepoAndPolicy) => { - const truncated = _.truncate(name, { length: 20 }); + const truncated = _.truncate(name, { length: 80 }); return ( this.setState({ showFlyout: true, flyoutSnapshotId: name, flyoutSnapshotRepo: item.repository })}> {truncated} @@ -84,7 +84,7 @@ export default class Snapshots extends Component name: "Status", sortable: true, dataType: "string", - width: "150px", + width: "130px", render: (value: string) => { return snapshotStatusRender(value); }, @@ -94,6 +94,7 @@ export default class Snapshots extends Component name: "Policy", sortable: false, dataType: "string", + width: "150px", render: (name: string, item: SnapshotsWithRepoAndPolicy) => { const truncated = _.truncate(name, { length: 20 }); if (!!item.policy) { @@ -106,6 +107,7 @@ export default class Snapshots extends Component field: "repository", name: "Repository", sortable: false, + width: "120px", dataType: "string", }, { @@ -113,6 +115,7 @@ export default class Snapshots extends Component name: "Start time", sortable: true, dataType: "date", + width: "130px", render: renderTimestampMillis, }, { @@ -120,6 +123,7 @@ export default class Snapshots extends Component name: "End time", sortable: true, dataType: "date", + width: "130px", render: renderTimestampMillis, }, ]; @@ -267,7 +271,11 @@ export default class Snapshots extends Component const subTitleText = (

- Snapshots are taken automatically from snapshot policies, or you can initiate manual snapshots to save to a repository. + Snapshots are taken automatically from snapshot policies, or you can initiate manual snapshots to save to a repository.
+ To restore a snapshot, use the snapshot restore API.{" "} + + Learn more +

); diff --git a/public/plugin.ts b/public/plugin.ts index ea5d09a53..ce07e27e6 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -7,6 +7,7 @@ import { AppMountParameters, CoreSetup, CoreStart, Plugin, PluginInitializerCont import { IndexManagementPluginSetup } from "."; import { IndexManagementPluginStart } from "."; import { actionRepoSingleton } from "./pages/VisualCreatePolicy/utils/helpers"; +import { ROUTES } from "./utils/constants"; export class IndexManagementPlugin implements Plugin { constructor(private readonly initializerContext: PluginInitializerContext) { @@ -26,9 +27,26 @@ export class IndexManagementPlugin implements Plugin { const { renderApp } = await import("./index_management_app"); const [coreStart, depsStart] = await core.getStartServices(); - return renderApp(coreStart, params); + return renderApp(coreStart, params, ROUTES.INDEX_POLICIES); }, }); + + core.application.register({ + id: "opensearch_snapshot_management_dashboards", + title: "Snapshot Management", + order: 7000, + category: { + id: "opensearch", + label: "OpenSearch Plugins", + order: 2000, + }, + mount: async (params: AppMountParameters) => { + const { renderApp } = await import("./index_management_app"); + const [coreStart, depsStart] = await core.getStartServices(); + return renderApp(coreStart, params, ROUTES.SNAPSHOT_POLICIES); + }, + }); + return { registerAction: (actionType, uiActionCtor, defaultAction) => { actionRepoSingleton.registerAction(actionType, uiActionCtor, defaultAction); diff --git a/public/services/NotificationService.ts b/public/services/NotificationService.ts index 64f453039..751f2af44 100644 --- a/public/services/NotificationService.ts +++ b/public/services/NotificationService.ts @@ -4,7 +4,7 @@ */ import { HttpSetup } from "opensearch-dashboards/public"; -import { GetChannelsResponse } from "../../server/models/interfaces"; +import { GetChannelsResponse, GetNotificationConfigsResponse } from "../../server/models/interfaces"; import { ServerResponse } from "../../server/models/types"; import { NODE_API } from "../../utils/constants"; @@ -20,4 +20,10 @@ export default class NotificationService { const response = (await this.httpClient.get(url)) as ServerResponse; return response; }; + + getChannel = async (channelId: string): Promise> => { + let url = `..${NODE_API.CHANNELS}/${channelId}`; + const response = (await this.httpClient.get(url)) as ServerResponse; + return response; + }; } diff --git a/public/utils/constants.ts b/public/utils/constants.ts index b697cd372..441de3385 100644 --- a/public/utils/constants.ts +++ b/public/utils/constants.ts @@ -13,11 +13,15 @@ export const ACTIONS_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/ export const STATES_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/ism/policies/#states"; export const ERROR_NOTIFICATION_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/ism/policies/#error-notifications"; export const TRANSITION_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/ism/policies/#transitions"; + +export const SNAPSHOT_MANAGEMENT_DOCUMENTATION_URL = "https://opensearch.org//docs/latest/opensearch/snapshots/snapshot-management/"; export const CRON_EXPRESSION_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/monitoring-plugins/alerting/cron/"; -export const SNAPSHOT_MANAGEMENT_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/ism/index"; -export const REPOSITORY_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/opensearch/snapshot-restore/#register-repository"; -export const FS_REPOSITORY_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/opensearch/snapshot-restore/#shared-file-system"; -export const S3_REPOSITORY_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/opensearch/snapshot-restore/#amazon-s3"; +export const RESTORE_SNAPSHOT_DOCUMENTATION_URL = + "https://opensearch.org/docs/latest/opensearch/snapshots/snapshot-restore/#restore-snapshots"; +export const REPOSITORY_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/opensearch/snapshots/snapshot-restore/#register-repository"; +export const FS_REPOSITORY_DOCUMENTATION_URL = + "https://opensearch.org/docs/latest/opensearch/snapshots/snapshot-restore/#shared-file-system"; +export const S3_REPOSITORY_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/opensearch/snapshots/snapshot-restore/#amazon-s3"; export const ROUTES = Object.freeze({ CHANGE_POLICY: "/change-policy", diff --git a/server/clusters/ism/ismPlugin.ts b/server/clusters/ism/ismPlugin.ts index e79a6c7e1..231c7b5bd 100644 --- a/server/clusters/ism/ismPlugin.ts +++ b/server/clusters/ism/ismPlugin.ts @@ -376,6 +376,19 @@ export default function ismPlugin(Client: any, config: any, components: any) { method: "GET", }); + ism.getChannel = ca({ + url: { + fmt: `${API.NOTIFICATION_CONFIGS_BASE}/<%=id%>`, + req: { + id: { + type: "string", + required: true, + }, + }, + }, + method: "GET", + }); + ism.getSMPolicy = ca({ url: { fmt: `${API.SM_POLICY_BASE}/<%=id%>`, diff --git a/server/models/interfaces.ts b/server/models/interfaces.ts index 4960d9967..f93626352 100644 --- a/server/models/interfaces.ts +++ b/server/models/interfaces.ts @@ -107,6 +107,25 @@ export interface FeatureChannelList { is_enabled: boolean; } +export interface GetNotificationConfigsResponse { + start_index: number; + total_hits: number; + total_hit_relation: string; + config_list: NotificationConfig[]; +} + +export interface NotificationConfig { + config_id: string; + last_updated_time_ms: number; + created_time_ms: number; + config: { + name: string; + description: string; + config_type: string; + is_enabled: boolean; + }; +} + export interface GetFieldsResponse { result: string; } @@ -282,6 +301,7 @@ export interface IndexManagementApi { readonly ROLLUP_JOBS_BASE: string; readonly TRANSFORM_BASE: string; readonly CHANNELS_BASE: string; + readonly NOTIFICATION_CONFIGS_BASE: string; readonly SM_POLICY_BASE: string; } diff --git a/server/routes/notifications.ts b/server/routes/notifications.ts index 0e52e4519..90f56498f 100644 --- a/server/routes/notifications.ts +++ b/server/routes/notifications.ts @@ -6,6 +6,7 @@ import { NodeServices } from "../models/interfaces"; import { NODE_API } from "../../utils/constants"; import { IRouter } from "../../../../src/core/server"; +import { schema } from "@osd/config-schema"; export default function (services: NodeServices, router: IRouter) { const { notificationService } = services; @@ -17,4 +18,16 @@ export default function (services: NodeServices, router: IRouter) { }, notificationService.getChannels ); + + router.get( + { + path: `${NODE_API.CHANNELS}/{id}`, + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + notificationService.getChannelById + ); } diff --git a/server/services/NotificationService.ts b/server/services/NotificationService.ts index 26c3d6fb8..b7d39a37a 100644 --- a/server/services/NotificationService.ts +++ b/server/services/NotificationService.ts @@ -12,7 +12,7 @@ import { ResponseError, } from "opensearch-dashboards/server"; import { ServerResponse } from "../models/types"; -import { GetChannelsResponse } from "../models/interfaces"; +import { GetChannelsResponse, GetNotificationConfigsResponse } from "../models/interfaces"; export default class NotificationService { osDriver: ILegacyCustomClusterClient; @@ -48,4 +48,38 @@ export default class NotificationService { }); } }; + + getChannelById = async ( + context: RequestHandlerContext, + request: OpenSearchDashboardsRequest, + response: OpenSearchDashboardsResponseFactory + ): Promise | ResponseError>> => { + try { + const { id } = request.params as { + id: string; + }; + + const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request); + const getResponse: GetNotificationConfigsResponse = await callWithRequest("ism.getChannel", { + id, + }); + + return response.custom({ + statusCode: 200, + body: { + ok: true, + response: getResponse, + }, + }); + } catch (err) { + console.error("Index Management - NotificationService - getChannel:", err); + return response.custom({ + statusCode: 200, + body: { + ok: false, + error: err.message, + }, + }); + } + }; } diff --git a/server/services/SnapshotManagementService.ts b/server/services/SnapshotManagementService.ts index 3d4324f2a..84d405fca 100644 --- a/server/services/SnapshotManagementService.ts +++ b/server/services/SnapshotManagementService.ts @@ -75,10 +75,6 @@ export default class SnapshotManagementService { repository: repositories[i], policy: s.metadata?.sm_policy, })); - // TODO SM try catch the missing snapshot exception - // const catSnapshotsRes: CatSnapshotWithRepoAndPolicy[] = await callWithRequest("snapshot.get", params); - // const snapshotsWithRepo = catSnapshotsRes.map((item) => ({ ...item, repository: repositories[i] })); - // console.log(`sm dev cat snapshot response: ${JSON.stringify(snapshotWithPolicy)}`); snapshots = [...snapshots, ...snapshotWithPolicy]; } @@ -93,7 +89,7 @@ export default class SnapshotManagementService { }, }); } catch (err) { - // TODO SM handle missing snapshot exception, return empty + // If getting a non-existing snapshot, need to handle the missing snapshot exception, and return empty return this.errorResponse(response, err, "getAllSnapshotsWithPolicy"); } }; @@ -176,7 +172,6 @@ export default class SnapshotManagementService { snapshot: id, body: JSON.stringify(request.body), }; - // TODO SM body indices, ignore_unavailable, include_global_state, partial const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request); const resp: CreateSnapshotResponse = await callWithRequest("snapshot.create", params); diff --git a/server/utils/constants.ts b/server/utils/constants.ts index 8196ce0b2..2bef33314 100644 --- a/server/utils/constants.ts +++ b/server/utils/constants.ts @@ -10,6 +10,7 @@ export const API_ROUTE_PREFIX_ROLLUP = "/_plugins/_rollup"; export const TRANSFORM_ROUTE_PREFIX = "/_plugins/_transform"; export const NOTIFICATIONS_API_ROUTE_PREFIX = "/_plugins/_notifications"; export const CHANNELS_ROUTE = `${NOTIFICATIONS_API_ROUTE_PREFIX}/channels`; +export const NOTIFICATION_CONFIGS_ROUTE = `${NOTIFICATIONS_API_ROUTE_PREFIX}/configs`; export const SM_ROUTE_PREFIX = "/_plugins/_sm"; export const API: IndexManagementApi = { @@ -22,6 +23,7 @@ export const API: IndexManagementApi = { ROLLUP_JOBS_BASE: `${API_ROUTE_PREFIX_ROLLUP}/jobs`, TRANSFORM_BASE: `${TRANSFORM_ROUTE_PREFIX}`, CHANNELS_BASE: `${CHANNELS_ROUTE}`, + NOTIFICATION_CONFIGS_BASE: `${NOTIFICATION_CONFIGS_ROUTE}`, SM_POLICY_BASE: `${SM_ROUTE_PREFIX}/policies`, };