diff --git a/CHANGELOG.md b/CHANGELOG.md index ee4a56a40..0df14f1f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [Remotes] Test the remote automatically on changes [#3323](https://github.com/vatesfr/xen-orchestra/issues/3323) (PR [#3397](https://github.com/vatesfr/xen-orchestra/pull/3397)) - [Remotes] Use *WORKGROUP* as default domain for new SMB remote (PR [#3398](https://github.com/vatesfr/xen-orchestra/pull/3398)) - [Backup NG form] Display a tip to encourage users to create vms on a thin-provisioned storage [#3334](https://github.com/vatesfr/xen-orchestra/issues/3334) (PR [#3402](https://github.com/vatesfr/xen-orchestra/pull/3402)) +- [Backup NG form] improve schedule's form [#3138](https://github.com/vatesfr/xen-orchestra/issues/3138) (PR [#3359](https://github.com/vatesfr/xen-orchestra/pull/3359)) ### Bug fixes diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index c0990e500..285c62864 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -349,6 +349,7 @@ const messages = { missingSnapshotRetention: 'The Rolling Snapshot mode requires snapshot retention to be higher than 0!', retentionNeeded: 'One of the retentions needs to be higher than 0!', + newScheduleError: 'Invalid schedule', createRemoteMessage: 'No remotes found, please click on the remotes settings button to create one!', remotesSettings: 'Remotes settings', diff --git a/packages/xo-web/src/xo-app/backup-ng/new/index.js b/packages/xo-web/src/xo-app/backup-ng/new/index.js index 4af4871a1..28b2691bb 100644 --- a/packages/xo-web/src/xo-app/backup-ng/new/index.js +++ b/packages/xo-web/src/xo-app/backup-ng/new/index.js @@ -3,6 +3,7 @@ import ActionButton from 'action-button' import defined, { get } from 'xo-defined' import Icon from 'icon' import Link from 'link' +import moment from 'moment-timezone' import React from 'react' import renderXoItem, { renderXoItemFromId } from 'render-xo-item' import Select from 'form/select' @@ -12,7 +13,9 @@ import { Card, CardBlock, CardHeader } from 'card' import { constructSmartPattern, destructSmartPattern } from 'smart-backup' import { Container, Col, Row } from 'grid' import { createGetObjectsOfType } from 'selectors' +import { error } from 'notification' import { flatten, includes, isEmpty, keyBy, map, mapValues, some } from 'lodash' +import { form } from 'modal' import { injectState, provideState } from '@julien-f/freactal' import { Map } from 'immutable' import { Number } from 'form' @@ -34,10 +37,10 @@ import { subscribeRemotes, } from 'xo' +import NewSchedule from './new-schedule' import Schedules from './schedules' import SmartBackup from './smart-backup' import { - DEFAULT_RETENTION, destructPattern, FormFeedback, FormGroup, @@ -48,6 +51,15 @@ import { // =================================================================== +const DEFAULT_RETENTION = 1 +const DEFAULT_SCHEDULE = { + copyRetention: DEFAULT_RETENTION, + exportRetention: DEFAULT_RETENTION, + snapshotRetention: DEFAULT_RETENTION, + cron: '0 0 * * *', + timezone: moment.tz.guess(), +} + const SR_BACKEND_FAILURE_LINK = 'https://xen-orchestra.com/docs/backup_troubleshooting.html#srbackendfailure44-insufficient-space' @@ -135,7 +147,6 @@ const getInitialState = () => ({ crMode: false, deltaMode: false, drMode: false, - editionMode: undefined, formId: generateRandomId(), inputConcurrencyId: generateRandomId(), inputReportWhenId: generateRandomId(), @@ -153,7 +164,6 @@ const getInitialState = () => ({ tags: { notValues: ['Continuous Replication', 'Disaster Recovery'], }, - tmpSchedule: undefined, vms: [], }) @@ -386,22 +396,32 @@ export default [ ...destructVmsPattern(job.vms), } }, - addSchedule: () => state => ({ - ...state, - editionMode: 'creation', - }), - cancelSchedule: () => state => ({ - ...state, - tmpSchedule: undefined, - editionMode: undefined, - }), - editSchedule: (_, schedule) => state => ({ - ...state, - editionMode: 'editSchedule', - tmpSchedule: { - ...schedule, - }, - }), + showScheduleModal: ( + { saveSchedule }, + storedSchedule = DEFAULT_SCHEDULE + ) => async ({ copyMode, exportMode, snapshotMode }) => { + const schedule = await form({ + body: , + defaultValue: storedSchedule, + icon: 'schedule', + size: 'large', + title: _('schedule'), + }) + if ( + !( + (exportMode && schedule.exportRetention > 0) || + (copyMode && schedule.copyRetention > 0) || + (snapshotMode && schedule.snapshotRetention > 0) + ) + ) { + error(_('newScheduleError'), _('retentionNeeded')) + } else { + saveSchedule({ + ...schedule, + id: storedSchedule.id || generateRandomId(), + }) + } + }, deleteSchedule: (_, schedule) => ({ schedules: oldSchedules, propSettings, @@ -415,47 +435,34 @@ export default [ settings: settings.delete(id), } }, - saveSchedule: (_, { cron, timezone, name, ...setting }) => ({ - editionMode, - propSettings, - schedules: oldSchedules, - settings = propSettings, - tmpSchedule, - }) => { - if (editionMode === 'creation') { - const id = generateRandomId() - return { - editionMode: undefined, - schedules: { - ...oldSchedules, - [id]: { - cron, - id, - name, - timezone, - }, - }, - settings: settings.set(id, setting), - } - } - - const id = tmpSchedule.id - const schedules = { ...oldSchedules } - - schedules[id] = { - ...schedules[id], + saveSchedule: ( + _, + { + copyRetention, cron, + exportRetention, + id, name, + snapshotRetention, timezone, } - - return { - editionMode: undefined, - schedules, - settings: settings.set(id, setting), - tmpSchedule: undefined, - } - }, + ) => ({ propSettings, schedules, settings = propSettings }) => ({ + schedules: { + ...schedules, + [id]: { + ...schedules[id], + cron, + id, + name, + timezone, + }, + }, + settings: settings.set(id, { + exportRetention, + copyRetention, + snapshotRetention, + }), + }), setPowerState: (_, powerState) => state => ({ ...state, powerState, diff --git a/packages/xo-web/src/xo-app/backup-ng/new/new-schedule.js b/packages/xo-web/src/xo-app/backup-ng/new/new-schedule.js index 1485f9b19..bcef36f25 100644 --- a/packages/xo-web/src/xo-app/backup-ng/new/new-schedule.js +++ b/packages/xo-web/src/xo-app/backup-ng/new/new-schedule.js @@ -1,6 +1,4 @@ import _ from 'intl' -import ActionButton from 'action-button' -import moment from 'moment-timezone' import React from 'react' import Scheduler, { SchedulePreview } from 'scheduling' import { Card, CardBlock } from 'card' @@ -8,34 +6,21 @@ import { generateRandomId } from 'utils' import { injectState, provideState } from '@julien-f/freactal' import { Number } from 'form' -import { DEFAULT_RETENTION, FormFeedback, FormGroup, Input } from './../utils' - -const DEFAULT_SCHEDULE = { - copyRetention: DEFAULT_RETENTION, - exportRetention: DEFAULT_RETENTION, - snapshotRetention: DEFAULT_RETENTION, - cron: '0 0 * * *', - timezone: moment.tz.guess(), -} +import { FormGroup, Input } from './../utils' export default [ - injectState, provideState({ initialState: () => ({ formId: generateRandomId(), idInputName: generateRandomId(), - schedule: undefined, }), effects: { - setSchedule: (_, params) => ({ - tmpSchedule = DEFAULT_SCHEDULE, - schedule = tmpSchedule, - }) => ({ - schedule: { - ...schedule, + setSchedule: (_, params) => (_, { value, onChange }) => { + onChange({ + ...value, ...params, - }, - }), + }) + }, setExportRetention: ({ setSchedule }, exportRetention) => () => { setSchedule({ exportRetention, @@ -66,120 +51,67 @@ export default [ }) }, }, - computed: { - isScheduleInvalid: ({ retentionNeeded, scheduleNotEdited }) => - retentionNeeded || scheduleNotEdited, - retentionNeeded: ({ copyMode, exportMode, schedule, snapshotMode }) => - schedule !== undefined && - !( - (exportMode && schedule.exportRetention > 0) || - (copyMode && schedule.copyRetention > 0) || - (snapshotMode && schedule.snapshotRetention > 0) - ), - scheduleNotEdited: ({ editionMode, schedule }) => - editionMode !== 'creation' && schedule === undefined, - }, }), injectState, - ({ effects, state }) => { - const { tmpSchedule = DEFAULT_SCHEDULE, schedule = tmpSchedule } = state - const { - copyRetention, - cron, - exportRetention, - name, - snapshotRetention, - timezone, - } = schedule - - return ( -
- - - - - - - {state.exportMode && ( - - - - - )} - {state.copyMode && ( - - - - - )} - {state.snapshotMode && ( - - - - - )} - ( + + + + + + + {modes.exportMode && ( + + + - -
- - {_('formSave')} - - - {_('formCancel')} - -
-
-
- ) - }, + + )} + {modes.copyMode && ( + + + + + )} + {modes.snapshotMode && ( + + + + + )} + + + + + ), ].reduceRight((value, decorator) => decorator(value)) diff --git a/packages/xo-web/src/xo-app/backup-ng/new/schedules.js b/packages/xo-web/src/xo-app/backup-ng/new/schedules.js index 1b64acee6..d9f2751f2 100644 --- a/packages/xo-web/src/xo-app/backup-ng/new/schedules.js +++ b/packages/xo-web/src/xo-app/backup-ng/new/schedules.js @@ -7,7 +7,6 @@ import { Card, CardBlock, CardHeader } from 'card' import { injectState, provideState } from '@julien-f/freactal' import { isEmpty, find, size } from 'lodash' -import NewSchedule from './new-schedule' import { FormFeedback } from './../utils' // =================================================================== @@ -25,16 +24,15 @@ export default [ computed: { disabledDeletion: state => size(state.schedules) <= 1, disabledEdition: state => - state.editionMode !== undefined || - (!state.exportMode && !state.copyMode && !state.snapshotMode), + !state.exportMode && !state.copyMode && !state.snapshotMode, error: state => find(FEEDBACK_ERRORS, error => state[error]), individualActions: ( { disabledDeletion, disabledEdition }, - { effects: { deleteSchedule, editSchedule } } + { effects: { deleteSchedule, showScheduleModal } } ) => [ { disabled: disabledEdition, - handler: editSchedule, + handler: showScheduleModal, icon: 'edit', label: _('scheduleEdit'), level: 'primary', @@ -129,7 +127,7 @@ export default [ - {state.editionMode !== undefined && } ), ].reduceRight((value, decorator) => decorator(value)) diff --git a/packages/xo-web/src/xo-app/backup-ng/utils.js b/packages/xo-web/src/xo-app/backup-ng/utils.js index 3d2f03cd4..f39b5fef3 100644 --- a/packages/xo-web/src/xo-app/backup-ng/utils.js +++ b/packages/xo-web/src/xo-app/backup-ng/utils.js @@ -2,8 +2,6 @@ import Icon from 'icon' import PropTypes from 'prop-types' import React from 'react' -export const DEFAULT_RETENTION = 1 - export const FormGroup = props =>
export const Input = props => export const Ul = props =>