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 (
-
- )
- },
+
+ )}
+ {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 =>