feat(Backups NG): fourth iteration (#2756)

This commit is contained in:
badrAZ 2018-03-16 16:23:19 +01:00 committed by Julien Fontanet
parent b58b1d94cd
commit 7d4b17380d
10 changed files with 383 additions and 273 deletions

View File

@ -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:

View File

@ -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'

View File

@ -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

View File

@ -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'

View File

@ -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>

View File

@ -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,

View File

@ -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),

View 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))

View File

@ -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'

View File

@ -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'