parent
899cec8814
commit
efffbafa42
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
||||||
|
|
||||||
|
- [Backup] **BETA** Ability to backup running VMs with their memory [#645](https://github.com/vatesfr/xen-orchestra/issues/645) (PR [#4252](https://github.com/vatesfr/xen-orchestra/pull/4252))
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
||||||
|
@ -28,6 +28,7 @@ import {
|
|||||||
flatMap,
|
flatMap,
|
||||||
flatten,
|
flatten,
|
||||||
groupBy,
|
groupBy,
|
||||||
|
identity,
|
||||||
includes,
|
includes,
|
||||||
isEmpty,
|
isEmpty,
|
||||||
noop,
|
noop,
|
||||||
@ -502,51 +503,63 @@ export default class Xapi extends XapiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Low level create VM.
|
// Low level create VM.
|
||||||
_createVmRecord({
|
_createVmRecord(
|
||||||
actions_after_crash,
|
{
|
||||||
actions_after_reboot,
|
actions_after_crash,
|
||||||
actions_after_shutdown,
|
actions_after_reboot,
|
||||||
affinity,
|
actions_after_shutdown,
|
||||||
// appliance,
|
affinity,
|
||||||
blocked_operations,
|
// appliance,
|
||||||
generation_id,
|
blocked_operations,
|
||||||
ha_always_run,
|
domain_type, // Used when the VM is created Suspended
|
||||||
ha_restart_priority,
|
generation_id,
|
||||||
has_vendor_device = false, // Avoid issue with some Dundee builds.
|
ha_always_run,
|
||||||
hardware_platform_version,
|
ha_restart_priority,
|
||||||
HVM_boot_params,
|
has_vendor_device = false, // Avoid issue with some Dundee builds.
|
||||||
HVM_boot_policy,
|
hardware_platform_version,
|
||||||
HVM_shadow_multiplier,
|
HVM_boot_params,
|
||||||
is_a_template,
|
HVM_boot_policy,
|
||||||
memory_dynamic_max,
|
HVM_shadow_multiplier,
|
||||||
memory_dynamic_min,
|
is_a_template,
|
||||||
memory_static_max,
|
last_boot_CPU_flags, // Used when the VM is created Suspended
|
||||||
memory_static_min,
|
last_booted_record, // Used when the VM is created Suspended
|
||||||
name_description,
|
memory_dynamic_max,
|
||||||
name_label,
|
memory_dynamic_min,
|
||||||
order,
|
memory_static_max,
|
||||||
other_config,
|
memory_static_min,
|
||||||
PCI_bus,
|
name_description,
|
||||||
platform,
|
name_label,
|
||||||
protection_policy,
|
order,
|
||||||
PV_args,
|
other_config,
|
||||||
PV_bootloader,
|
PCI_bus,
|
||||||
PV_bootloader_args,
|
platform,
|
||||||
PV_kernel,
|
protection_policy,
|
||||||
PV_legacy_args,
|
PV_args,
|
||||||
PV_ramdisk,
|
PV_bootloader,
|
||||||
recommendations,
|
PV_bootloader_args,
|
||||||
shutdown_delay,
|
PV_kernel,
|
||||||
start_delay,
|
PV_legacy_args,
|
||||||
// suspend_SR,
|
PV_ramdisk,
|
||||||
tags,
|
recommendations,
|
||||||
user_version,
|
shutdown_delay,
|
||||||
VCPUs_at_startup,
|
start_delay,
|
||||||
VCPUs_max,
|
// suspend_SR,
|
||||||
VCPUs_params,
|
tags,
|
||||||
version,
|
user_version,
|
||||||
xenstore_data,
|
VCPUs_at_startup,
|
||||||
}) {
|
VCPUs_max,
|
||||||
|
VCPUs_params,
|
||||||
|
version,
|
||||||
|
xenstore_data,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// if set, will create the VM in Suspended power_state with this VDI
|
||||||
|
//
|
||||||
|
// it's a separate param because it's not supported for all versions of
|
||||||
|
// XCP-ng/XenServer and should be passed explicitly
|
||||||
|
suspend_VDI,
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
log.debug(`Creating VM ${name_label}`)
|
log.debug(`Creating VM ${name_label}`)
|
||||||
|
|
||||||
return this.call(
|
return this.call(
|
||||||
@ -598,6 +611,13 @@ export default class Xapi extends XapiBase {
|
|||||||
tags,
|
tags,
|
||||||
version: asInteger(version),
|
version: asInteger(version),
|
||||||
xenstore_data,
|
xenstore_data,
|
||||||
|
|
||||||
|
// VM created Suspended
|
||||||
|
power_state: suspend_VDI !== undefined ? 'Suspended' : undefined,
|
||||||
|
suspend_VDI,
|
||||||
|
domain_type,
|
||||||
|
last_boot_CPU_flags,
|
||||||
|
last_booted_record,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -894,6 +914,17 @@ export default class Xapi extends XapiBase {
|
|||||||
this._exportVdi($cancelToken, vdi, baseVdi, VDI_FORMAT_VHD)
|
this._exportVdi($cancelToken, vdi, baseVdi, VDI_FORMAT_VHD)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const suspendVdi = vm.$suspend_VDI
|
||||||
|
if (suspendVdi !== undefined) {
|
||||||
|
const vdiRef = suspendVdi.$ref
|
||||||
|
vdis[vdiRef] = {
|
||||||
|
...suspendVdi,
|
||||||
|
$SR$uuid: suspendVdi.$SR.uuid,
|
||||||
|
}
|
||||||
|
streams[`${vdiRef}.vhd`] = () =>
|
||||||
|
this._exportVdi($cancelToken, suspendVdi, undefined, VDI_FORMAT_VHD)
|
||||||
|
}
|
||||||
|
|
||||||
const vifs = {}
|
const vifs = {}
|
||||||
forEach(vm.$VIFs, vif => {
|
forEach(vm.$VIFs, vif => {
|
||||||
const network = vif.$network
|
const network = vif.$network
|
||||||
@ -980,23 +1011,42 @@ export default class Xapi extends XapiBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 0. Create suspend_VDI
|
||||||
|
let suspendVdi
|
||||||
|
if (delta.vm.power_state === 'Suspended') {
|
||||||
|
const vdi = delta.vdis[delta.vm.suspend_VDI]
|
||||||
|
suspendVdi = await this.createVdi({
|
||||||
|
...vdi,
|
||||||
|
other_config: {
|
||||||
|
...vdi.other_config,
|
||||||
|
[TAG_BASE_DELTA]: undefined,
|
||||||
|
[TAG_COPY_SRC]: vdi.uuid,
|
||||||
|
},
|
||||||
|
sr: mapVdisSrs[vdi.uuid] || srId,
|
||||||
|
})
|
||||||
|
$defer.onFailure.call(this, '_deleteVdi', suspendVdi.$ref)
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Create the VMs.
|
// 1. Create the VMs.
|
||||||
const vm = await this._getOrWaitObject(
|
const vm = await this._getOrWaitObject(
|
||||||
await this._createVmRecord({
|
await this._createVmRecord(
|
||||||
...delta.vm,
|
{
|
||||||
affinity: null,
|
...delta.vm,
|
||||||
blocked_operations: {
|
affinity: null,
|
||||||
...delta.vm.blocked_operations,
|
blocked_operations: {
|
||||||
start: 'Importing…',
|
...delta.vm.blocked_operations,
|
||||||
|
start: 'Importing…',
|
||||||
|
},
|
||||||
|
ha_always_run: false,
|
||||||
|
is_a_template: false,
|
||||||
|
name_label: `[Importing…] ${name_label}`,
|
||||||
|
other_config: {
|
||||||
|
...delta.vm.other_config,
|
||||||
|
[TAG_COPY_SRC]: delta.vm.uuid,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ha_always_run: false,
|
{ suspend_VDI: suspendVdi?.$ref }
|
||||||
is_a_template: false,
|
)
|
||||||
name_label: `[Importing…] ${name_label}`,
|
|
||||||
other_config: {
|
|
||||||
...delta.vm.other_config,
|
|
||||||
[TAG_COPY_SRC]: delta.vm.uuid,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
$defer.onFailure(() => this._deleteVm(vm))
|
$defer.onFailure(() => this._deleteVm(vm))
|
||||||
|
|
||||||
@ -1004,8 +1054,10 @@ export default class Xapi extends XapiBase {
|
|||||||
await asyncMap(vm.$VBDs, vbd => this._deleteVbd(vbd))::ignoreErrors()
|
await asyncMap(vm.$VBDs, vbd => this._deleteVbd(vbd))::ignoreErrors()
|
||||||
|
|
||||||
// 3. Create VDIs & VBDs.
|
// 3. Create VDIs & VBDs.
|
||||||
|
//
|
||||||
|
// TODO: move all VDIs creation before the VM and simplify the code
|
||||||
const vbds = groupBy(delta.vbds, 'VDI')
|
const vbds = groupBy(delta.vbds, 'VDI')
|
||||||
const newVdis = await map(delta.vdis, async (vdi, vdiId) => {
|
const newVdis = await map(delta.vdis, async (vdi, vdiRef) => {
|
||||||
let newVdi
|
let newVdi
|
||||||
|
|
||||||
const remoteBaseVdiUuid = detectBase && vdi.other_config[TAG_BASE_DELTA]
|
const remoteBaseVdiUuid = detectBase && vdi.other_config[TAG_BASE_DELTA]
|
||||||
@ -1022,6 +1074,9 @@ export default class Xapi extends XapiBase {
|
|||||||
$defer.onFailure(() => this._deleteVdi(newVdi.$ref))
|
$defer.onFailure(() => this._deleteVdi(newVdi.$ref))
|
||||||
|
|
||||||
await newVdi.update_other_config(TAG_COPY_SRC, vdi.uuid)
|
await newVdi.update_other_config(TAG_COPY_SRC, vdi.uuid)
|
||||||
|
} else if (vdiRef === delta.vm.suspend_VDI) {
|
||||||
|
// suspend VDI has been already created
|
||||||
|
newVdi = suspendVdi
|
||||||
} else {
|
} else {
|
||||||
newVdi = await this.createVdi({
|
newVdi = await this.createVdi({
|
||||||
...vdi,
|
...vdi,
|
||||||
@ -1035,7 +1090,7 @@ export default class Xapi extends XapiBase {
|
|||||||
$defer.onFailure(() => this._deleteVdi(newVdi.$ref))
|
$defer.onFailure(() => this._deleteVdi(newVdi.$ref))
|
||||||
}
|
}
|
||||||
|
|
||||||
await asyncMap(vbds[vdiId], vbd =>
|
await asyncMap(vbds[vdiRef], vbd =>
|
||||||
this.createVbd({
|
this.createVbd({
|
||||||
...vbd,
|
...vbd,
|
||||||
vdi: newVdi,
|
vdi: newVdi,
|
||||||
@ -1653,6 +1708,8 @@ export default class Xapi extends XapiBase {
|
|||||||
|
|
||||||
async createVbd({
|
async createVbd({
|
||||||
bootable = false,
|
bootable = false,
|
||||||
|
currently_attached = false,
|
||||||
|
device = '',
|
||||||
other_config = {},
|
other_config = {},
|
||||||
qos_algorithm_params = {},
|
qos_algorithm_params = {},
|
||||||
qos_algorithm_type = '',
|
qos_algorithm_type = '',
|
||||||
@ -1693,9 +1750,13 @@ export default class Xapi extends XapiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ifVmSuspended = vm.power_state === 'Suspended' ? identity : noop
|
||||||
|
|
||||||
// By default a VBD is unpluggable.
|
// By default a VBD is unpluggable.
|
||||||
const vbdRef = await this.call('VBD.create', {
|
const vbdRef = await this.call('VBD.create', {
|
||||||
bootable: Boolean(bootable),
|
bootable: Boolean(bootable),
|
||||||
|
currently_attached: ifVmSuspended(currently_attached),
|
||||||
|
device: ifVmSuspended(device),
|
||||||
empty: Boolean(empty),
|
empty: Boolean(empty),
|
||||||
mode,
|
mode,
|
||||||
other_config,
|
other_config,
|
||||||
@ -2045,6 +2106,8 @@ export default class Xapi extends XapiBase {
|
|||||||
const vifRef = await this.call(
|
const vifRef = await this.call(
|
||||||
'VIF.create',
|
'VIF.create',
|
||||||
filterUndefineds({
|
filterUndefineds({
|
||||||
|
currently_attached:
|
||||||
|
vm.power_state === 'Suspended' ? currently_attached : undefined,
|
||||||
device,
|
device,
|
||||||
ipv4_allowed,
|
ipv4_allowed,
|
||||||
ipv6_allowed,
|
ipv6_allowed,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import deferrable from 'golike-defer'
|
import deferrable from 'golike-defer'
|
||||||
import { find, gte, includes, isEmpty, lte, noop } from 'lodash'
|
import { find, gte, includes, isEmpty, lte, noop } from 'lodash'
|
||||||
import { ignoreErrors, pCatch } from 'promise-toolbox'
|
import { cancelable, ignoreErrors, pCatch } from 'promise-toolbox'
|
||||||
import { NULL_REF } from 'xen-api'
|
import { NULL_REF } from 'xen-api'
|
||||||
|
|
||||||
import { forEach, mapToArray, parseSize } from '../../utils'
|
import { forEach, mapToArray, parseSize } from '../../utils'
|
||||||
@ -18,10 +18,12 @@ const XEN_VIDEORAM_VALUES = [1, 2, 4, 8, 16]
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
// https://xapi-project.github.io/xen-api/classes/vm.html#checkpoint
|
// https://xapi-project.github.io/xen-api/classes/vm.html#checkpoint
|
||||||
async checkpointVm(vmId, nameLabel) {
|
@cancelable
|
||||||
|
async checkpointVm($cancelToken, vmId, nameLabel) {
|
||||||
const vm = this.getObject(vmId)
|
const vm = this.getObject(vmId)
|
||||||
try {
|
try {
|
||||||
const ref = await this.callAsync(
|
const ref = await this.callAsync(
|
||||||
|
$cancelToken,
|
||||||
'VM.checkpoint',
|
'VM.checkpoint',
|
||||||
vm.$ref,
|
vm.$ref,
|
||||||
nameLabel != null ? nameLabel : vm.name_label
|
nameLabel != null ? nameLabel : vm.name_label
|
||||||
@ -29,7 +31,7 @@ export default {
|
|||||||
return this.barrier(ref)
|
return this.barrier(ref)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'VM_BAD_POWER_STATE') {
|
if (error.code === 'VM_BAD_POWER_STATE') {
|
||||||
return this._snapshotVm(vm, nameLabel)
|
return this._snapshotVm($cancelToken, vm, nameLabel)
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,7 @@ export type ReportWhen = 'always' | 'failure' | 'never'
|
|||||||
|
|
||||||
type Settings = {|
|
type Settings = {|
|
||||||
bypassVdiChainsCheck?: boolean,
|
bypassVdiChainsCheck?: boolean,
|
||||||
|
checkpointSnapshot?: boolean,
|
||||||
concurrency?: number,
|
concurrency?: number,
|
||||||
deleteFirst?: boolean,
|
deleteFirst?: boolean,
|
||||||
copyRetention?: number,
|
copyRetention?: number,
|
||||||
@ -149,6 +150,7 @@ const getOldEntries = <T>(retention: number, entries?: T[]): T[] =>
|
|||||||
|
|
||||||
const defaultSettings: Settings = {
|
const defaultSettings: Settings = {
|
||||||
bypassVdiChainsCheck: false,
|
bypassVdiChainsCheck: false,
|
||||||
|
checkpointSnapshot: false,
|
||||||
concurrency: 0,
|
concurrency: 0,
|
||||||
deleteFirst: false,
|
deleteFirst: false,
|
||||||
exportRetention: 0,
|
exportRetention: 0,
|
||||||
@ -1208,6 +1210,9 @@ export default class BackupNg {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkpointSnapshot =
|
||||||
|
!offlineSnapshot &&
|
||||||
|
getSetting(settings, 'checkpointSnapshot', [vmUuid, ''])
|
||||||
exported = (await wrapTask(
|
exported = (await wrapTask(
|
||||||
{
|
{
|
||||||
logger,
|
logger,
|
||||||
@ -1215,11 +1220,17 @@ export default class BackupNg {
|
|||||||
parentId: taskId,
|
parentId: taskId,
|
||||||
result: _ => _.uuid,
|
result: _ => _.uuid,
|
||||||
},
|
},
|
||||||
xapi._snapshotVm(
|
checkpointSnapshot
|
||||||
$cancelToken,
|
? xapi.checkpointVm(
|
||||||
vm,
|
$cancelToken,
|
||||||
`[XO Backup ${job.name}] ${vm.name_label}`
|
vm.$id,
|
||||||
)
|
`[XO Backup ${job.name}] ${vm.name_label}`
|
||||||
|
)
|
||||||
|
: xapi._snapshotVm(
|
||||||
|
$cancelToken,
|
||||||
|
vm,
|
||||||
|
`[XO Backup ${job.name}] ${vm.name_label}`
|
||||||
|
)
|
||||||
): any)
|
): any)
|
||||||
|
|
||||||
if (startAfterSnapshot) {
|
if (startAfterSnapshot) {
|
||||||
@ -1641,7 +1652,12 @@ export default class BackupNg {
|
|||||||
deltaExport.vdis,
|
deltaExport.vdis,
|
||||||
vdi =>
|
vdi =>
|
||||||
`vdis/${jobId}/${
|
`vdis/${jobId}/${
|
||||||
(xapi.getObject(vdi.snapshot_of): Object).uuid
|
(vdi.type === 'suspend'
|
||||||
|
? // doesn't make sense to group by parent for memory because we
|
||||||
|
// don't do delta for it
|
||||||
|
vdi
|
||||||
|
: (xapi.getObject(vdi.snapshot_of): Object)
|
||||||
|
).uuid
|
||||||
}/${basename}.vhd`
|
}/${basename}.vhd`
|
||||||
),
|
),
|
||||||
vm,
|
vm,
|
||||||
|
@ -72,6 +72,10 @@ const messages = {
|
|||||||
altered: 'Altered',
|
altered: 'Altered',
|
||||||
missing: 'Missing',
|
missing: 'Missing',
|
||||||
verified: 'Verified',
|
verified: 'Verified',
|
||||||
|
snapshotMode: 'Snapshot mode',
|
||||||
|
normal: 'Normal',
|
||||||
|
withMemory: 'With memory',
|
||||||
|
offline: 'Offline',
|
||||||
|
|
||||||
// ----- Modals -----
|
// ----- Modals -----
|
||||||
alertOk: 'OK',
|
alertOk: 'OK',
|
||||||
@ -479,8 +483,8 @@ const messages = {
|
|||||||
smartBackup: 'Smart backup',
|
smartBackup: 'Smart backup',
|
||||||
snapshotRetention: 'Snapshot retention',
|
snapshotRetention: 'Snapshot retention',
|
||||||
backupName: 'Name',
|
backupName: 'Name',
|
||||||
|
checkpointSnapshot: 'Checkpoint snapshot',
|
||||||
offlineSnapshot: 'Offline snapshot',
|
offlineSnapshot: 'Offline snapshot',
|
||||||
offlineSnapshotInfo: 'Shutdown VMs before snapshotting them',
|
|
||||||
offlineBackup: 'Offline backup',
|
offlineBackup: 'Offline backup',
|
||||||
offlineBackupInfo:
|
offlineBackupInfo:
|
||||||
'Export VMs without snapshotting them. The VMs will be shutdown during the export.',
|
'Export VMs without snapshotting them. The VMs will be shutdown during the export.',
|
||||||
|
@ -3,6 +3,7 @@ import { pickBy } from 'lodash'
|
|||||||
const DEFAULTS = {
|
const DEFAULTS = {
|
||||||
__proto__: null,
|
__proto__: null,
|
||||||
|
|
||||||
|
checkpointSnapshot: false,
|
||||||
compression: '',
|
compression: '',
|
||||||
concurrency: 0,
|
concurrency: 0,
|
||||||
fullInterval: 0,
|
fullInterval: 0,
|
||||||
|
73
packages/xo-web/src/xo-app/backup/new/_selectSnapshotMode.js
Normal file
73
packages/xo-web/src/xo-app/backup/new/_selectSnapshotMode.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import _ from 'intl'
|
||||||
|
import decorate from 'apply-decorators'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import { CURRENT, PREMIUM } from 'xoa-plans'
|
||||||
|
import { generateId } from 'reaclette-utils'
|
||||||
|
import { injectState, provideState } from 'reaclette'
|
||||||
|
import { Select } from 'form'
|
||||||
|
|
||||||
|
import { FormGroup } from '../utils'
|
||||||
|
|
||||||
|
const OPTIONS = [
|
||||||
|
{
|
||||||
|
label: _('normal'),
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
disabled: CURRENT.value < PREMIUM.value,
|
||||||
|
label: _('withMemory'),
|
||||||
|
value: 'checkpointSnapshot',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _('offline'),
|
||||||
|
value: 'offlineSnapshot',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const SelectSnapshotMode = decorate([
|
||||||
|
provideState({
|
||||||
|
effects: {
|
||||||
|
setMode(_, value) {
|
||||||
|
this.props.setGlobalSettings({
|
||||||
|
offlineSnapshot: value === 'offlineSnapshot',
|
||||||
|
checkpointSnapshot: value === 'checkpointSnapshot',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
idSelect: generateId,
|
||||||
|
value: (_, { checkpointSnapshot, offlineSnapshot }) =>
|
||||||
|
checkpointSnapshot
|
||||||
|
? 'checkpointSnapshot'
|
||||||
|
: offlineSnapshot
|
||||||
|
? 'offlineSnapshot'
|
||||||
|
: '',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
injectState,
|
||||||
|
({ state, effects, ...props }) => (
|
||||||
|
<FormGroup>
|
||||||
|
<label htmlFor={state.idSelect}>
|
||||||
|
<strong>{_('snapshotMode')}</strong>
|
||||||
|
</label>{' '}
|
||||||
|
<Select
|
||||||
|
{...props}
|
||||||
|
id={state.idSelect}
|
||||||
|
onChange={effects.setMode}
|
||||||
|
options={OPTIONS}
|
||||||
|
required
|
||||||
|
simpleValue
|
||||||
|
value={state.value}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
SelectSnapshotMode.propTypes = {
|
||||||
|
checkpointSnapshot: PropTypes.bool,
|
||||||
|
offlineSnapshot: PropTypes.bool,
|
||||||
|
setGlobalSettings: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SelectSnapshotMode as default }
|
@ -54,6 +54,8 @@ import NewSchedule from './new-schedule'
|
|||||||
import ReportWhen from './_reportWhen'
|
import ReportWhen from './_reportWhen'
|
||||||
import Schedules from './schedules'
|
import Schedules from './schedules'
|
||||||
import SmartBackup from './smart-backup'
|
import SmartBackup from './smart-backup'
|
||||||
|
import SelectSnapshotMode from './_selectSnapshotMode'
|
||||||
|
|
||||||
import getSettingsWithNonDefaultValue from '../_getSettingsWithNonDefaultValue'
|
import getSettingsWithNonDefaultValue from '../_getSettingsWithNonDefaultValue'
|
||||||
import {
|
import {
|
||||||
canDeltaBackup,
|
canDeltaBackup,
|
||||||
@ -623,13 +625,13 @@ export default decorate([
|
|||||||
toggleDisplayAdvancedSettings: () => ({ displayAdvancedSettings }) => ({
|
toggleDisplayAdvancedSettings: () => ({ displayAdvancedSettings }) => ({
|
||||||
_displayAdvancedSettings: !displayAdvancedSettings,
|
_displayAdvancedSettings: !displayAdvancedSettings,
|
||||||
}),
|
}),
|
||||||
setGlobalSettings: (_, { name, value }) => ({
|
setGlobalSettings: (_, globalSettings) => ({
|
||||||
propSettings,
|
propSettings,
|
||||||
settings = propSettings,
|
settings = propSettings,
|
||||||
}) => ({
|
}) => ({
|
||||||
settings: settings.update('', setting => ({
|
settings: settings.update('', setting => ({
|
||||||
...setting,
|
...setting,
|
||||||
[name]: value,
|
...globalSettings,
|
||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
addReportRecipient({ setGlobalSettings }, value) {
|
addReportRecipient({ setGlobalSettings }, value) {
|
||||||
@ -640,8 +642,7 @@ export default decorate([
|
|||||||
)
|
)
|
||||||
if (!reportRecipients.includes(value)) {
|
if (!reportRecipients.includes(value)) {
|
||||||
setGlobalSettings({
|
setGlobalSettings({
|
||||||
name: 'reportRecipients',
|
reportRecipients: (reportRecipients.push(value), reportRecipients),
|
||||||
value: (reportRecipients.push(value), reportRecipients),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -649,50 +650,35 @@ export default decorate([
|
|||||||
const { propSettings, settings = propSettings } = this.state
|
const { propSettings, settings = propSettings } = this.state
|
||||||
const reportRecipients = settings.getIn(['', 'reportRecipients'])
|
const reportRecipients = settings.getIn(['', 'reportRecipients'])
|
||||||
setGlobalSettings({
|
setGlobalSettings({
|
||||||
name: 'reportRecipients',
|
reportRecipients: (reportRecipients.splice(key, 1), reportRecipients),
|
||||||
value: (reportRecipients.splice(key, 1), reportRecipients),
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setReportWhen: ({ setGlobalSettings }, { value }) => () => {
|
setReportWhen: ({ setGlobalSettings }, { value }) => () => {
|
||||||
setGlobalSettings({
|
setGlobalSettings({
|
||||||
name: 'reportWhen',
|
reportWhen: value,
|
||||||
value,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setConcurrency: ({ setGlobalSettings }, value) => () => {
|
setConcurrency: ({ setGlobalSettings }, concurrency) => () => {
|
||||||
setGlobalSettings({
|
setGlobalSettings({
|
||||||
name: 'concurrency',
|
concurrency,
|
||||||
value,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setTimeout: ({ setGlobalSettings }, value) => () => {
|
setTimeout: ({ setGlobalSettings }, value) => () => {
|
||||||
setGlobalSettings({
|
setGlobalSettings({
|
||||||
name: 'timeout',
|
timeout: value && value * 3600e3,
|
||||||
value: value && value * 3600e3,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setFullInterval({ setGlobalSettings }, value) {
|
setFullInterval({ setGlobalSettings }, fullInterval) {
|
||||||
setGlobalSettings({
|
setGlobalSettings({
|
||||||
name: 'fullInterval',
|
fullInterval,
|
||||||
value,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setOfflineSnapshot: (
|
|
||||||
{ setGlobalSettings },
|
|
||||||
{ target: { checked: value } }
|
|
||||||
) => () => {
|
|
||||||
setGlobalSettings({
|
|
||||||
name: 'offlineSnapshot',
|
|
||||||
value,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setOfflineBackup: (
|
setOfflineBackup: (
|
||||||
{ setGlobalSettings },
|
{ setGlobalSettings },
|
||||||
{ target: { checked: value } }
|
{ target: { checked: offlineBackup } }
|
||||||
) => () => {
|
) => () => {
|
||||||
setGlobalSettings({
|
setGlobalSettings({
|
||||||
name: 'offlineBackup',
|
offlineBackup,
|
||||||
value,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -819,6 +805,7 @@ export default decorate([
|
|||||||
const { propSettings, settings = propSettings } = state
|
const { propSettings, settings = propSettings } = state
|
||||||
const compression = defined(state.compression, job.compression, '')
|
const compression = defined(state.compression, job.compression, '')
|
||||||
const {
|
const {
|
||||||
|
checkpointSnapshot,
|
||||||
concurrency,
|
concurrency,
|
||||||
fullInterval,
|
fullInterval,
|
||||||
offlineBackup,
|
offlineBackup,
|
||||||
@ -1150,6 +1137,7 @@ export default decorate([
|
|||||||
</Tooltip>{' '}
|
</Tooltip>{' '}
|
||||||
<input
|
<input
|
||||||
checked={offlineBackup}
|
checked={offlineBackup}
|
||||||
|
disabled={offlineSnapshot || checkpointSnapshot}
|
||||||
onChange={effects.setOfflineBackup}
|
onChange={effects.setOfflineBackup}
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
/>
|
/>
|
||||||
@ -1157,21 +1145,12 @@ export default decorate([
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!state.offlineBackupActive && (
|
<SelectSnapshotMode
|
||||||
<FormGroup>
|
checkpointSnapshot={checkpointSnapshot}
|
||||||
<label>
|
disabled={state.offlineBackupActive}
|
||||||
<strong>{_('offlineSnapshot')}</strong>{' '}
|
offlineSnapshot={offlineSnapshot}
|
||||||
<Tooltip content={_('offlineSnapshotInfo')}>
|
setGlobalSettings={effects.setGlobalSettings}
|
||||||
<Icon icon='info' />
|
/>
|
||||||
</Tooltip>{' '}
|
|
||||||
<input
|
|
||||||
checked={offlineSnapshot}
|
|
||||||
onChange={effects.setOfflineSnapshot}
|
|
||||||
type='checkbox'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardBlock>
|
</CardBlock>
|
||||||
|
@ -247,6 +247,7 @@ class JobsTable extends React.Component {
|
|||||||
{
|
{
|
||||||
itemRenderer: job => {
|
itemRenderer: job => {
|
||||||
const {
|
const {
|
||||||
|
checkpointSnapshot,
|
||||||
compression,
|
compression,
|
||||||
concurrency,
|
concurrency,
|
||||||
fullInterval,
|
fullInterval,
|
||||||
@ -294,6 +295,14 @@ class JobsTable extends React.Component {
|
|||||||
)}
|
)}
|
||||||
</Li>
|
</Li>
|
||||||
)}
|
)}
|
||||||
|
{checkpointSnapshot !== undefined && (
|
||||||
|
<Li>
|
||||||
|
{_.keyValue(
|
||||||
|
_('checkpointSnapshot'),
|
||||||
|
_(checkpointSnapshot ? 'stateEnabled' : 'stateDisabled')
|
||||||
|
)}
|
||||||
|
</Li>
|
||||||
|
)}
|
||||||
{compression !== undefined && (
|
{compression !== undefined && (
|
||||||
<Li>
|
<Li>
|
||||||
{_.keyValue(
|
{_.keyValue(
|
||||||
|
Loading…
Reference in New Issue
Block a user