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