feat(Backups NG): fourth iteration (#2756)
This commit is contained in:
parent
b58b1d94cd
commit
7d4b17380d
@ -280,6 +280,7 @@ const messages = {
|
|||||||
jobInterrupted: 'Interrupted',
|
jobInterrupted: 'Interrupted',
|
||||||
jobStarted: 'Started',
|
jobStarted: 'Started',
|
||||||
saveBackupJob: 'Save',
|
saveBackupJob: 'Save',
|
||||||
|
resetBackupJob: 'Reset',
|
||||||
createBackupJob: 'Create',
|
createBackupJob: 'Create',
|
||||||
deleteBackupSchedule: 'Remove backup job',
|
deleteBackupSchedule: 'Remove backup job',
|
||||||
deleteBackupScheduleQuestion:
|
deleteBackupScheduleQuestion:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as CM from 'complex-matcher'
|
import * as CM from 'complex-matcher'
|
||||||
import { get, identity, isEmpty } from 'lodash'
|
import { get, identity, isEmpty } from 'lodash'
|
||||||
|
|
||||||
import { EMPTY_OBJECT } from './utils'
|
import { EMPTY_OBJECT } from './../utils'
|
||||||
|
|
||||||
export const destructPattern = (pattern, valueTransform = identity) =>
|
export const destructPattern = (pattern, valueTransform = identity) =>
|
||||||
pattern && {
|
pattern && {
|
||||||
@ -106,3 +106,7 @@ export const constructQueryString = pattern => {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
export default from './preview'
|
@ -5,13 +5,13 @@ import { createPredicate } from 'value-matcher'
|
|||||||
import { createSelector } from 'reselect'
|
import { createSelector } from 'reselect'
|
||||||
import { filter, map, pickBy } from 'lodash'
|
import { filter, map, pickBy } from 'lodash'
|
||||||
|
|
||||||
import Component from './base-component'
|
import Component from './../base-component'
|
||||||
import Icon from './icon'
|
import Icon from './../icon'
|
||||||
import Link from './link'
|
import Link from './../link'
|
||||||
import renderXoItem from './render-xo-item'
|
import renderXoItem from './../render-xo-item'
|
||||||
import Tooltip from './tooltip'
|
import Tooltip from './../tooltip'
|
||||||
import { Card, CardBlock, CardHeader } from './card'
|
import { Card, CardBlock, CardHeader } from './../card'
|
||||||
import { constructQueryString } from './smart-backup-pattern'
|
import { constructQueryString } from './index'
|
||||||
|
|
||||||
const SAMPLE_SIZE_OF_MATCHING_VMS = 3
|
const SAMPLE_SIZE_OF_MATCHING_VMS = 3
|
||||||
|
|
@ -7,7 +7,7 @@ import SortedTable from 'sorted-table'
|
|||||||
import StateButton from 'state-button'
|
import StateButton from 'state-button'
|
||||||
import { map, groupBy } from 'lodash'
|
import { map, groupBy } from 'lodash'
|
||||||
import { Card, CardHeader, CardBlock } from 'card'
|
import { Card, CardHeader, CardBlock } from 'card'
|
||||||
import { constructQueryString } from 'smart-backup-pattern'
|
import { constructQueryString } from 'smart-backup'
|
||||||
import { Container, Row, Col } from 'grid'
|
import { Container, Row, Col } from 'grid'
|
||||||
import { NavLink, NavTabs } from 'nav'
|
import { NavLink, NavTabs } from 'nav'
|
||||||
import { routes } from 'utils'
|
import { routes } from 'utils'
|
||||||
|
@ -2,27 +2,16 @@ import _ from 'intl'
|
|||||||
import ActionButton from 'action-button'
|
import ActionButton from 'action-button'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import renderXoItem, { renderXoItemFromId } from 'render-xo-item'
|
import renderXoItem, { renderXoItemFromId } from 'render-xo-item'
|
||||||
import SmartBackupPreview from 'smart-backup-preview'
|
|
||||||
import Tooltip from 'tooltip'
|
import Tooltip from 'tooltip'
|
||||||
import Upgrade from 'xoa-upgrade'
|
import Upgrade from 'xoa-upgrade'
|
||||||
import { addSubscriptions, connectStore, resolveId, resolveIds } from 'utils'
|
import { addSubscriptions, resolveId, resolveIds } from 'utils'
|
||||||
import { Card, CardBlock, CardHeader } from 'card'
|
import { Card, CardBlock, CardHeader } from 'card'
|
||||||
import { Container, Col, Row } from 'grid'
|
import { Container, Col, Row } from 'grid'
|
||||||
import { createGetObjectsOfType } from 'selectors'
|
import { find, findKey, flatten, keyBy, isEmpty, map, some } from 'lodash'
|
||||||
import { flatten, get, keyBy, isEmpty, map, some } from 'lodash'
|
|
||||||
import { injectState, provideState } from '@julien-f/freactal'
|
import { injectState, provideState } from '@julien-f/freactal'
|
||||||
import { Select, Toggle } from 'form'
|
import { Toggle } from 'form'
|
||||||
import {
|
import { constructSmartPattern, destructSmartPattern } from 'smart-backup'
|
||||||
constructSmartPattern,
|
import { SelectRemote, SelectSr, SelectVm } from 'select-objects'
|
||||||
destructSmartPattern,
|
|
||||||
} from 'smart-backup-pattern'
|
|
||||||
import {
|
|
||||||
SelectPool,
|
|
||||||
SelectRemote,
|
|
||||||
SelectSr,
|
|
||||||
SelectTag,
|
|
||||||
SelectVm,
|
|
||||||
} from 'select-objects'
|
|
||||||
import {
|
import {
|
||||||
createBackupNgJob,
|
createBackupNgJob,
|
||||||
createSchedule,
|
createSchedule,
|
||||||
@ -33,133 +22,13 @@ import {
|
|||||||
} from 'xo'
|
} from 'xo'
|
||||||
|
|
||||||
import Schedules from './schedules'
|
import Schedules from './schedules'
|
||||||
|
import SmartBackup from './smart-backup'
|
||||||
import { FormGroup, getRandomId, Input, Ul, Li } from './utils'
|
import { FormGroup, getRandomId, Input, Ul, Li } from './utils'
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const SMART_MODE_INITIAL_STATE = {
|
|
||||||
powerState: 'All',
|
|
||||||
$pool: {},
|
|
||||||
tags: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
const SMART_MODE_FUNCTIONS = {
|
|
||||||
setPowerState: (_, powerState) => state => ({
|
|
||||||
...state,
|
|
||||||
powerState,
|
|
||||||
}),
|
|
||||||
setPoolValues: (_, values) => state => ({
|
|
||||||
...state,
|
|
||||||
$pool: {
|
|
||||||
...state.$pool,
|
|
||||||
values,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
setPoolNotValues: (_, notValues) => state => ({
|
|
||||||
...state,
|
|
||||||
$pool: {
|
|
||||||
...state.$pool,
|
|
||||||
notValues,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
setTagValues: (_, values) => state => ({
|
|
||||||
...state,
|
|
||||||
tags: {
|
|
||||||
...state.tags,
|
|
||||||
values,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
setTagNotValues: (_, notValues) => state => ({
|
|
||||||
...state,
|
|
||||||
tags: {
|
|
||||||
...state.tags,
|
|
||||||
notValues,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
const normaliseTagValues = values => resolveIds(values).map(value => [value])
|
const normaliseTagValues = values => resolveIds(values).map(value => [value])
|
||||||
|
|
||||||
const SMART_MODE_COMPUTED = {
|
|
||||||
vmsSmartPattern: ({ $pool, powerState, tags }) => ({
|
|
||||||
$pool: constructSmartPattern($pool, resolveIds),
|
|
||||||
power_state: powerState === 'All' ? undefined : powerState,
|
|
||||||
tags: constructSmartPattern(tags, normaliseTagValues),
|
|
||||||
type: 'VM',
|
|
||||||
}),
|
|
||||||
allVms: (state, { allVms }) => allVms,
|
|
||||||
}
|
|
||||||
|
|
||||||
const VMS_STATUSES_OPTIONS = [
|
|
||||||
{ value: 'All', label: _('vmStateAll') },
|
|
||||||
{ value: 'Running', label: _('vmStateRunning') },
|
|
||||||
{ value: 'Halted', label: _('vmStateHalted') },
|
|
||||||
]
|
|
||||||
|
|
||||||
const SmartBackup = injectState(({ state, effects }) => (
|
|
||||||
<div>
|
|
||||||
<FormGroup>
|
|
||||||
<label>
|
|
||||||
<strong>{_('editBackupSmartStatusTitle')}</strong>
|
|
||||||
</label>
|
|
||||||
<Select
|
|
||||||
options={VMS_STATUSES_OPTIONS}
|
|
||||||
onChange={effects.setPowerState}
|
|
||||||
value={state.powerState}
|
|
||||||
simpleValue
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<h3>{_('editBackupSmartPools')}</h3>
|
|
||||||
<hr />
|
|
||||||
<FormGroup>
|
|
||||||
<label>
|
|
||||||
<strong>{_('editBackupSmartResidentOn')}</strong>
|
|
||||||
</label>
|
|
||||||
<SelectPool
|
|
||||||
multi
|
|
||||||
onChange={effects.setPoolValues}
|
|
||||||
value={get(state.$pool, 'values')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup>
|
|
||||||
<label>
|
|
||||||
<strong>{_('editBackupSmartNotResidentOn')}</strong>
|
|
||||||
</label>
|
|
||||||
<SelectPool
|
|
||||||
multi
|
|
||||||
onChange={effects.setPoolNotValues}
|
|
||||||
value={get(state.$pool, 'notValues')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<h3>{_('editBackupSmartTags')}</h3>
|
|
||||||
<hr />
|
|
||||||
<FormGroup>
|
|
||||||
<label>
|
|
||||||
<strong>{_('editBackupSmartTagsTitle')}</strong>
|
|
||||||
</label>
|
|
||||||
<SelectTag
|
|
||||||
multi
|
|
||||||
onChange={effects.setTagValues}
|
|
||||||
value={get(state.tags, 'values')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup>
|
|
||||||
<label>
|
|
||||||
<strong>{_('editBackupSmartExcludedTagsTitle')}</strong>
|
|
||||||
</label>
|
|
||||||
<SelectTag
|
|
||||||
multi
|
|
||||||
onChange={effects.setTagNotValues}
|
|
||||||
value={get(state.tags, 'notValues')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<SmartBackupPreview vms={state.allVms} pattern={state.vmsSmartPattern} />
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
const constructPattern = values => ({
|
const constructPattern = values => ({
|
||||||
id: {
|
id: {
|
||||||
__or: resolveIds(values),
|
__or: resolveIds(values),
|
||||||
@ -182,10 +51,10 @@ const destructVmsPattern = pattern =>
|
|||||||
const getNewSettings = schedules => {
|
const getNewSettings = schedules => {
|
||||||
const newSettings = {}
|
const newSettings = {}
|
||||||
|
|
||||||
for (const schedule in schedules) {
|
for (const id in schedules) {
|
||||||
newSettings[schedule] = {
|
newSettings[id] = {
|
||||||
exportRetention: +schedules[schedule].exportRetention,
|
exportRetention: +schedules[id].exportRetention,
|
||||||
snapshotRetention: +schedules[schedule].snapshotRetention,
|
snapshotRetention: +schedules[id].snapshotRetention,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,25 +64,46 @@ const getNewSettings = schedules => {
|
|||||||
const getNewSchedules = schedules => {
|
const getNewSchedules = schedules => {
|
||||||
const newSchedules = {}
|
const newSchedules = {}
|
||||||
|
|
||||||
for (const schedule in schedules) {
|
for (const id in schedules) {
|
||||||
newSchedules[schedule] = {
|
newSchedules[id] = {
|
||||||
cron: schedules[schedule].cron,
|
cron: schedules[id].cron,
|
||||||
timezone: schedules[schedule].timezone,
|
timezone: schedules[id].timezone,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newSchedules
|
return newSchedules
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getInitialState = () => ({
|
||||||
|
$pool: {},
|
||||||
|
backupMode: undefined,
|
||||||
|
compression: true,
|
||||||
|
crMode: undefined,
|
||||||
|
deltaMode: undefined,
|
||||||
|
drMode: undefined,
|
||||||
|
editionMode: undefined,
|
||||||
|
formId: getRandomId(),
|
||||||
|
name: '',
|
||||||
|
newSchedules: {},
|
||||||
|
paramsUpdated: false,
|
||||||
|
powerState: 'All',
|
||||||
|
remotes: [],
|
||||||
|
schedules: [],
|
||||||
|
settings: {},
|
||||||
|
smartMode: false,
|
||||||
|
snapshotMode: undefined,
|
||||||
|
srs: [],
|
||||||
|
tags: {},
|
||||||
|
tmpSchedule: {},
|
||||||
|
vms: [],
|
||||||
|
})
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
New => props => (
|
New => props => (
|
||||||
<Upgrade place='newBackup' required={2}>
|
<Upgrade place='newBackup' required={2}>
|
||||||
<New {...props} />
|
<New {...props} />
|
||||||
</Upgrade>
|
</Upgrade>
|
||||||
),
|
),
|
||||||
connectStore({
|
|
||||||
allVms: createGetObjectsOfType('VM'),
|
|
||||||
}),
|
|
||||||
addSubscriptions({
|
addSubscriptions({
|
||||||
remotes: cb =>
|
remotes: cb =>
|
||||||
subscribeRemotes(remotes => {
|
subscribeRemotes(remotes => {
|
||||||
@ -221,25 +111,7 @@ export default [
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
provideState({
|
provideState({
|
||||||
initialState: () => ({
|
initialState: getInitialState,
|
||||||
compression: true,
|
|
||||||
backupMode: undefined,
|
|
||||||
drMode: undefined,
|
|
||||||
deltaMode: undefined,
|
|
||||||
crMode: undefined,
|
|
||||||
snapshotMode: undefined,
|
|
||||||
formId: getRandomId(),
|
|
||||||
name: '',
|
|
||||||
paramsUpdated: false,
|
|
||||||
remotes: [],
|
|
||||||
smartMode: false,
|
|
||||||
srs: [],
|
|
||||||
vms: [],
|
|
||||||
tmpSchedule: {},
|
|
||||||
newSchedules: {},
|
|
||||||
editionMode: undefined,
|
|
||||||
...SMART_MODE_INITIAL_STATE,
|
|
||||||
}),
|
|
||||||
effects: {
|
effects: {
|
||||||
createJob: () => async state => {
|
createJob: () => async state => {
|
||||||
await createBackupNgJob({
|
await createBackupNgJob({
|
||||||
@ -276,19 +148,68 @@ export default [
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
map(props.schedules, oldSchedule => {
|
||||||
|
const scheduleId = oldSchedule.id
|
||||||
|
const newSchedule = find(state.schedules, { id: scheduleId })
|
||||||
|
|
||||||
|
if (
|
||||||
|
newSchedule !== undefined &&
|
||||||
|
newSchedule.cron === oldSchedule.cron &&
|
||||||
|
newSchedule.timezone === oldSchedule.timezone
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSchedule === undefined) {
|
||||||
|
return deleteSchedule(scheduleId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return editSchedule({
|
||||||
|
id: scheduleId,
|
||||||
|
jobId: props.job.id,
|
||||||
|
cron: newSchedule.cron,
|
||||||
|
timezone: newSchedule.timezone,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const oldSettings = props.job.settings
|
||||||
|
const settings = state.settings
|
||||||
|
for (const id in oldSettings) {
|
||||||
|
const oldSetting = oldSettings[id]
|
||||||
|
const newSetting = settings[id]
|
||||||
|
|
||||||
|
if (!(id in settings)) {
|
||||||
|
delete oldSettings[id]
|
||||||
|
} else if (
|
||||||
|
oldSetting.snapshotRetention !== newSetting.snapshotRetention ||
|
||||||
|
oldSetting.exportRetention !== newSetting.exportRetention
|
||||||
|
) {
|
||||||
|
newSettings[id] = {
|
||||||
|
exportRetention: +newSetting.exportRetention,
|
||||||
|
snapshotRetention: +newSetting.snapshotRetention,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await editBackupNgJob({
|
await editBackupNgJob({
|
||||||
id: props.job.id,
|
id: props.job.id,
|
||||||
name: state.name,
|
name: state.name,
|
||||||
mode: state.isDelta ? 'delta' : 'full',
|
mode: state.isDelta ? 'delta' : 'full',
|
||||||
compression: state.compression ? 'native' : '',
|
compression: state.compression ? 'native' : '',
|
||||||
settings: {
|
settings: {
|
||||||
|
...oldSettings,
|
||||||
...newSettings,
|
...newSettings,
|
||||||
...props.job.settings,
|
|
||||||
},
|
},
|
||||||
remotes:
|
remotes:
|
||||||
(state.deltaMode || state.backupMode) &&
|
state.deltaMode || state.backupMode
|
||||||
constructPattern(state.remotes),
|
? constructPattern(state.remotes)
|
||||||
srs: (state.crMode || state.drMode) && constructPattern(state.srs),
|
: constructPattern([]),
|
||||||
|
srs:
|
||||||
|
state.crMode || state.drMode
|
||||||
|
? constructPattern(state.srs)
|
||||||
|
: constructPattern([]),
|
||||||
vms: state.smartMode
|
vms: state.smartMode
|
||||||
? state.vmsSmartPattern
|
? state.vmsSmartPattern
|
||||||
: constructPattern(state.vms),
|
: constructPattern(state.vms),
|
||||||
@ -359,10 +280,14 @@ export default [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
setVms: (_, vms) => state => ({ ...state, vms }),
|
setVms: (_, vms) => state => ({ ...state, vms }),
|
||||||
updateParams: () => (state, { job }) => ({
|
updateParams: () => (state, { job, schedules }) => {
|
||||||
|
const remotes =
|
||||||
|
job.remotes !== undefined ? destructPattern(job.remotes) : []
|
||||||
|
const srs = job.srs !== undefined ? destructPattern(job.srs) : []
|
||||||
|
|
||||||
|
return {
|
||||||
...state,
|
...state,
|
||||||
compression: job.compression === 'native',
|
compression: job.compression === 'native',
|
||||||
delta: job.mode === 'delta',
|
|
||||||
name: job.name,
|
name: job.name,
|
||||||
paramsUpdated: true,
|
paramsUpdated: true,
|
||||||
smartMode: job.vms.id === undefined,
|
smartMode: job.vms.id === undefined,
|
||||||
@ -371,14 +296,17 @@ export default [
|
|||||||
job.settings,
|
job.settings,
|
||||||
({ snapshotRetention }) => snapshotRetention > 0
|
({ snapshotRetention }) => snapshotRetention > 0
|
||||||
) || undefined,
|
) || undefined,
|
||||||
backupMode: (job.mode === 'full' && !isEmpty(job.remotes)) || undefined,
|
backupMode: (job.mode === 'full' && !isEmpty(remotes)) || undefined,
|
||||||
deltaMode: (job.mode === 'delta' && !isEmpty(job.remotes)) || undefined,
|
deltaMode: (job.mode === 'delta' && !isEmpty(remotes)) || undefined,
|
||||||
drMode: (job.mode === 'full' && !isEmpty(job.srs)) || undefined,
|
drMode: (job.mode === 'full' && !isEmpty(srs)) || undefined,
|
||||||
crMode: (job.mode === 'delta' && !isEmpty(job.srs)) || undefined,
|
crMode: (job.mode === 'delta' && !isEmpty(srs)) || undefined,
|
||||||
remotes: job.remotes !== undefined ? destructPattern(job.remotes) : [],
|
remotes,
|
||||||
srs: job.srs !== undefined ? destructPattern(job.srs) : [],
|
srs,
|
||||||
|
settings: job.settings,
|
||||||
|
schedules,
|
||||||
...destructVmsPattern(job.vms),
|
...destructVmsPattern(job.vms),
|
||||||
}),
|
}
|
||||||
|
},
|
||||||
addSchedule: () => state => ({
|
addSchedule: () => state => ({
|
||||||
...state,
|
...state,
|
||||||
editionMode: 'creation',
|
editionMode: 'creation',
|
||||||
@ -402,14 +330,13 @@ export default [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteSchedule: (_, id) => async (state, props) => {
|
deleteSchedule: (_, id) => async (state, props) => {
|
||||||
await deleteSchedule(id)
|
const schedules = [...state.schedules]
|
||||||
delete props.job.settings[id]
|
schedules.splice(findKey(state.schedules, { id }), 1)
|
||||||
await editBackupNgJob({
|
|
||||||
id: props.job.id,
|
return {
|
||||||
settings: {
|
...state,
|
||||||
...props.job.settings,
|
schedules,
|
||||||
},
|
}
|
||||||
})
|
|
||||||
},
|
},
|
||||||
editNewSchedule: (_, schedule) => state => ({
|
editNewSchedule: (_, schedule) => state => ({
|
||||||
...state,
|
...state,
|
||||||
@ -446,27 +373,27 @@ export default [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const id = state.tmpSchedule.id
|
||||||
if (state.editionMode === 'editSchedule') {
|
if (state.editionMode === 'editSchedule') {
|
||||||
await editSchedule({
|
const scheduleKey = findKey(state.schedules, { id })
|
||||||
id: state.tmpSchedule.id,
|
const schedules = [...state.schedules]
|
||||||
jobId: props.job.id,
|
schedules[scheduleKey] = {
|
||||||
|
...schedules[scheduleKey],
|
||||||
cron,
|
cron,
|
||||||
timezone,
|
timezone,
|
||||||
})
|
}
|
||||||
await editBackupNgJob({
|
|
||||||
id: props.job.id,
|
const settings = { ...state.settings }
|
||||||
settings: {
|
settings[id] = {
|
||||||
...props.job.settings,
|
exportRetention,
|
||||||
[state.tmpSchedule.id]: {
|
snapshotRetention,
|
||||||
exportRetention: +exportRetention,
|
}
|
||||||
snapshotRetention: +snapshotRetention,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
editionMode: undefined,
|
editionMode: undefined,
|
||||||
|
schedules,
|
||||||
|
settings,
|
||||||
tmpSchedule: {},
|
tmpSchedule: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -477,7 +404,7 @@ export default [
|
|||||||
tmpSchedule: {},
|
tmpSchedule: {},
|
||||||
newSchedules: {
|
newSchedules: {
|
||||||
...state.newSchedules,
|
...state.newSchedules,
|
||||||
[state.tmpSchedule.id]: {
|
[id]: {
|
||||||
cron,
|
cron,
|
||||||
timezone,
|
timezone,
|
||||||
exportRetention,
|
exportRetention,
|
||||||
@ -486,34 +413,85 @@ export default [
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...SMART_MODE_FUNCTIONS,
|
setPowerState: (_, powerState) => state => ({
|
||||||
|
...state,
|
||||||
|
powerState,
|
||||||
|
}),
|
||||||
|
setPoolValues: (_, values) => state => ({
|
||||||
|
...state,
|
||||||
|
$pool: {
|
||||||
|
...state.$pool,
|
||||||
|
values,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
setPoolNotValues: (_, notValues) => state => ({
|
||||||
|
...state,
|
||||||
|
$pool: {
|
||||||
|
...state.$pool,
|
||||||
|
notValues,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
setTagValues: (_, values) => state => ({
|
||||||
|
...state,
|
||||||
|
tags: {
|
||||||
|
...state.tags,
|
||||||
|
values,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
setTagNotValues: (_, notValues) => state => ({
|
||||||
|
...state,
|
||||||
|
tags: {
|
||||||
|
...state.tags,
|
||||||
|
notValues,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
resetJob: ({ updateParams }) => (state, { job }) => {
|
||||||
|
if (job !== undefined) {
|
||||||
|
updateParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
return getInitialState()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
needUpdateParams: (state, { job }) =>
|
needUpdateParams: (state, { job, schedules }) =>
|
||||||
job !== undefined && !state.paramsUpdated,
|
job !== undefined && schedules !== undefined && !state.paramsUpdated,
|
||||||
isJobInvalid: state =>
|
isJobInvalid: state =>
|
||||||
state.name.trim() === '' ||
|
state.name.trim() === '' ||
|
||||||
(isEmpty(state.schedules) && isEmpty(state.newSchedules)) ||
|
(isEmpty(state.schedules) && isEmpty(state.newSchedules)) ||
|
||||||
(isEmpty(state.vms) && !state.smartMode) ||
|
(isEmpty(state.vms) && !state.smartMode) ||
|
||||||
((state.backupMode || state.deltaMode) && isEmpty(state.remotes)) ||
|
((state.backupMode || state.deltaMode) && isEmpty(state.remotes)) ||
|
||||||
((state.drMode || state.crMode) && isEmpty(state.srs)) ||
|
((state.drMode || state.crMode) && isEmpty(state.srs)) ||
|
||||||
|
(state.exportMode && !state.exportRetentionExists) ||
|
||||||
|
(state.snapshotMode && !state.snapshotRetentionExists) ||
|
||||||
(!state.isDelta && !state.isFull && !state.snapshotMode),
|
(!state.isDelta && !state.isFull && !state.snapshotMode),
|
||||||
showCompression: (state, { job }) =>
|
showCompression: state => state.isFull && state.exportRetentionExists,
|
||||||
state.isFull &&
|
|
||||||
(some(
|
|
||||||
state.newSchedules,
|
|
||||||
schedule => +schedule.exportRetention !== 0
|
|
||||||
) ||
|
|
||||||
(job &&
|
|
||||||
some(job.settings, schedule => schedule.exportRetention !== 0))),
|
|
||||||
exportMode: state =>
|
exportMode: state =>
|
||||||
state.backupMode || state.deltaMode || state.drMode || state.crMode,
|
state.backupMode || state.deltaMode || state.drMode || state.crMode,
|
||||||
settings: (state, { job }) => get(job, 'settings') || {},
|
exportRetentionExists: state =>
|
||||||
schedules: (state, { schedules }) => schedules || [],
|
some(
|
||||||
|
state.newSchedules,
|
||||||
|
({ exportRetention }) => +exportRetention !== 0
|
||||||
|
) ||
|
||||||
|
some(state.settings, ({ exportRetention }) => +exportRetention !== 0),
|
||||||
|
snapshotRetentionExists: state =>
|
||||||
|
some(
|
||||||
|
state.newSchedules,
|
||||||
|
({ snapshotRetention }) => +snapshotRetention !== 0
|
||||||
|
) ||
|
||||||
|
some(
|
||||||
|
state.settings,
|
||||||
|
({ snapshotRetention }) => +snapshotRetention !== 0
|
||||||
|
),
|
||||||
isDelta: state => state.deltaMode || state.crMode,
|
isDelta: state => state.deltaMode || state.crMode,
|
||||||
isFull: state => state.backupMode || state.drMode,
|
isFull: state => state.backupMode || state.drMode,
|
||||||
allRemotes: (state, { remotes }) => remotes,
|
storedRemotes: (state, { remotes }) => remotes,
|
||||||
...SMART_MODE_COMPUTED,
|
vmsSmartPattern: ({ $pool, powerState, tags }) => ({
|
||||||
|
$pool: constructSmartPattern($pool, resolveIds),
|
||||||
|
power_state: powerState === 'All' ? undefined : powerState,
|
||||||
|
tags: constructSmartPattern(tags, normaliseTagValues),
|
||||||
|
type: 'VM',
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
injectState,
|
injectState,
|
||||||
@ -634,10 +612,10 @@ export default [
|
|||||||
<Ul>
|
<Ul>
|
||||||
{map(state.remotes, (id, key) => (
|
{map(state.remotes, (id, key) => (
|
||||||
<Li key={id}>
|
<Li key={id}>
|
||||||
{state.allRemotes &&
|
{state.storedRemotes &&
|
||||||
renderXoItem({
|
renderXoItem({
|
||||||
type: 'remote',
|
type: 'remote',
|
||||||
value: state.allRemotes[id],
|
value: state.storedRemotes[id],
|
||||||
})}
|
})}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
btnStyle='danger'
|
btnStyle='danger'
|
||||||
@ -695,6 +673,8 @@ export default [
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
|
<Card>
|
||||||
|
<CardBlock>
|
||||||
{state.paramsUpdated ? (
|
{state.paramsUpdated ? (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
btnStyle='primary'
|
btnStyle='primary'
|
||||||
@ -720,6 +700,16 @@ export default [
|
|||||||
{_('createBackupJob')}
|
{_('createBackupJob')}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
)}
|
)}
|
||||||
|
<ActionButton
|
||||||
|
handler={effects.resetJob}
|
||||||
|
icon='undo'
|
||||||
|
className='pull-right'
|
||||||
|
size='large'
|
||||||
|
>
|
||||||
|
{_('resetBackupJob')}
|
||||||
|
</ActionButton>
|
||||||
|
</CardBlock>
|
||||||
|
</Card>
|
||||||
</Row>
|
</Row>
|
||||||
</Container>
|
</Container>
|
||||||
</form>
|
</form>
|
||||||
|
@ -5,6 +5,7 @@ import React from 'react'
|
|||||||
import Scheduler, { SchedulePreview } from 'scheduling'
|
import Scheduler, { SchedulePreview } from 'scheduling'
|
||||||
import { Card, CardBlock } from 'card'
|
import { Card, CardBlock } from 'card'
|
||||||
import { injectState, provideState } from '@julien-f/freactal'
|
import { injectState, provideState } from '@julien-f/freactal'
|
||||||
|
import { isEqual } from 'lodash'
|
||||||
|
|
||||||
import { FormGroup, getRandomId, Input } from './utils'
|
import { FormGroup, getRandomId, Input } from './utils'
|
||||||
|
|
||||||
@ -19,6 +20,12 @@ export default [
|
|||||||
timezone = moment.tz.guess(),
|
timezone = moment.tz.guess(),
|
||||||
},
|
},
|
||||||
}) => ({
|
}) => ({
|
||||||
|
oldSchedule: {
|
||||||
|
cron,
|
||||||
|
exportRetention,
|
||||||
|
snapshotRetention,
|
||||||
|
timezone,
|
||||||
|
},
|
||||||
cron,
|
cron,
|
||||||
exportRetention,
|
exportRetention,
|
||||||
formId: getRandomId(),
|
formId: getRandomId(),
|
||||||
@ -41,9 +48,21 @@ export default [
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isScheduleInvalid: ({ snapshotRetention, exportRetention }) =>
|
isScheduleInvalid: ({
|
||||||
(+snapshotRetention === 0 || snapshotRetention === '') &&
|
cron,
|
||||||
(+exportRetention === 0 || exportRetention === ''),
|
exportRetention,
|
||||||
|
snapshotRetention,
|
||||||
|
timezone,
|
||||||
|
oldSchedule,
|
||||||
|
}) =>
|
||||||
|
((+snapshotRetention === 0 || snapshotRetention === '') &&
|
||||||
|
(+exportRetention === 0 || exportRetention === '')) ||
|
||||||
|
isEqual(oldSchedule, {
|
||||||
|
cron,
|
||||||
|
exportRetention,
|
||||||
|
snapshotRetention,
|
||||||
|
timezone,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
injectState,
|
injectState,
|
||||||
|
@ -4,7 +4,7 @@ import React from 'react'
|
|||||||
import SortedTable from 'sorted-table'
|
import SortedTable from 'sorted-table'
|
||||||
import { Card, CardBlock, CardHeader } from 'card'
|
import { Card, CardBlock, CardHeader } from 'card'
|
||||||
import { injectState, provideState } from '@julien-f/freactal'
|
import { injectState, provideState } from '@julien-f/freactal'
|
||||||
import { isEmpty, findKey } from 'lodash'
|
import { isEmpty, findKey, size } from 'lodash'
|
||||||
|
|
||||||
import NewSchedule from './new-schedule'
|
import NewSchedule from './new-schedule'
|
||||||
import { FormGroup } from './utils'
|
import { FormGroup } from './utils'
|
||||||
@ -98,7 +98,8 @@ export default [
|
|||||||
injectState,
|
injectState,
|
||||||
provideState({
|
provideState({
|
||||||
computed: {
|
computed: {
|
||||||
disabledDeletion: state => state.schedules.length <= 1,
|
disabledDeletion: state =>
|
||||||
|
state.schedules.length + size(state.newSchedules) <= 1,
|
||||||
disabledEdition: state =>
|
disabledEdition: state =>
|
||||||
state.editionMode !== undefined ||
|
state.editionMode !== undefined ||
|
||||||
(!state.exportMode && !state.snapshotMode),
|
(!state.exportMode && !state.snapshotMode),
|
||||||
|
93
packages/xo-web/src/xo-app/backup-ng/new/smart-backup.js
Normal file
93
packages/xo-web/src/xo-app/backup-ng/new/smart-backup.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import _ from 'intl'
|
||||||
|
import React from 'react'
|
||||||
|
import SmartBackupPreview from 'smart-backup'
|
||||||
|
import { connectStore } from 'utils'
|
||||||
|
import { createGetObjectsOfType } from 'selectors'
|
||||||
|
import { get } from 'lodash'
|
||||||
|
import { injectState, provideState } from '@julien-f/freactal'
|
||||||
|
import { Select } from 'form'
|
||||||
|
import { SelectPool, SelectTag } from 'select-objects'
|
||||||
|
|
||||||
|
import { FormGroup } from './utils'
|
||||||
|
|
||||||
|
const VMS_STATUSES_OPTIONS = [
|
||||||
|
{ value: 'All', label: _('vmStateAll') },
|
||||||
|
{ value: 'Running', label: _('vmStateRunning') },
|
||||||
|
{ value: 'Halted', label: _('vmStateHalted') },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default [
|
||||||
|
connectStore({
|
||||||
|
storedVms: createGetObjectsOfType('VM'),
|
||||||
|
}),
|
||||||
|
provideState({
|
||||||
|
computed: {
|
||||||
|
storedVms: (state, { storedVms }) => storedVms,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
injectState,
|
||||||
|
({ state, effects }) => (
|
||||||
|
<div>
|
||||||
|
<FormGroup>
|
||||||
|
<label>
|
||||||
|
<strong>{_('editBackupSmartStatusTitle')}</strong>
|
||||||
|
</label>
|
||||||
|
<Select
|
||||||
|
options={VMS_STATUSES_OPTIONS}
|
||||||
|
onChange={effects.setPowerState}
|
||||||
|
value={state.powerState}
|
||||||
|
simpleValue
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<h3>{_('editBackupSmartPools')}</h3>
|
||||||
|
<hr />
|
||||||
|
<FormGroup>
|
||||||
|
<label>
|
||||||
|
<strong>{_('editBackupSmartResidentOn')}</strong>
|
||||||
|
</label>
|
||||||
|
<SelectPool
|
||||||
|
multi
|
||||||
|
onChange={effects.setPoolValues}
|
||||||
|
value={get(state.$pool, 'values')}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<label>
|
||||||
|
<strong>{_('editBackupSmartNotResidentOn')}</strong>
|
||||||
|
</label>
|
||||||
|
<SelectPool
|
||||||
|
multi
|
||||||
|
onChange={effects.setPoolNotValues}
|
||||||
|
value={get(state.$pool, 'notValues')}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<h3>{_('editBackupSmartTags')}</h3>
|
||||||
|
<hr />
|
||||||
|
<FormGroup>
|
||||||
|
<label>
|
||||||
|
<strong>{_('editBackupSmartTagsTitle')}</strong>
|
||||||
|
</label>
|
||||||
|
<SelectTag
|
||||||
|
multi
|
||||||
|
onChange={effects.setTagValues}
|
||||||
|
value={get(state.tags, 'values')}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<label>
|
||||||
|
<strong>{_('editBackupSmartExcludedTagsTitle')}</strong>
|
||||||
|
</label>
|
||||||
|
<SelectTag
|
||||||
|
multi
|
||||||
|
onChange={effects.setTagNotValues}
|
||||||
|
value={get(state.tags, 'notValues')}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<SmartBackupPreview
|
||||||
|
vms={state.storedVms}
|
||||||
|
pattern={state.vmsSmartPattern}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
].reduceRight((value, decorator) => decorator(value))
|
@ -8,13 +8,15 @@ import Icon from 'icon'
|
|||||||
import moment from 'moment-timezone'
|
import moment from 'moment-timezone'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Scheduler, { SchedulePreview } from 'scheduling'
|
import Scheduler, { SchedulePreview } from 'scheduling'
|
||||||
import SmartBackupPreview from 'smart-backup-preview'
|
import SmartBackupPreview, {
|
||||||
|
constructPattern,
|
||||||
|
destructPattern,
|
||||||
|
} from 'smart-backup'
|
||||||
import uncontrollableInput from 'uncontrollable-input'
|
import uncontrollableInput from 'uncontrollable-input'
|
||||||
import Upgrade from 'xoa-upgrade'
|
import Upgrade from 'xoa-upgrade'
|
||||||
import Wizard, { Section } from 'wizard'
|
import Wizard, { Section } from 'wizard'
|
||||||
import { confirm } from 'modal'
|
import { confirm } from 'modal'
|
||||||
import { connectStore, EMPTY_OBJECT } from 'utils'
|
import { connectStore, EMPTY_OBJECT } from 'utils'
|
||||||
import { constructPattern, destructPattern } from 'smart-backup-pattern'
|
|
||||||
import { Container, Row, Col } from 'grid'
|
import { Container, Row, Col } from 'grid'
|
||||||
import { createGetObjectsOfType, getUser } from 'selectors'
|
import { createGetObjectsOfType, getUser } from 'selectors'
|
||||||
import { createSelector } from 'reselect'
|
import { createSelector } from 'reselect'
|
||||||
|
@ -11,7 +11,7 @@ import SortedTable from 'sorted-table'
|
|||||||
import StateButton from 'state-button'
|
import StateButton from 'state-button'
|
||||||
import Tooltip from 'tooltip'
|
import Tooltip from 'tooltip'
|
||||||
import { addSubscriptions } from 'utils'
|
import { addSubscriptions } from 'utils'
|
||||||
import { constructQueryString } from 'smart-backup-pattern'
|
import { constructQueryString } from 'smart-backup'
|
||||||
import { createSelector } from 'selectors'
|
import { createSelector } from 'selectors'
|
||||||
import { Card, CardHeader, CardBlock } from 'card'
|
import { Card, CardHeader, CardBlock } from 'card'
|
||||||
import { filter, find, forEach, get, keyBy, map, orderBy } from 'lodash'
|
import { filter, find, forEach, get, keyBy, map, orderBy } from 'lodash'
|
||||||
|
Loading…
Reference in New Issue
Block a user