diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js
index b9716b622..51efced5b 100644
--- a/packages/xo-web/src/common/intl/messages.js
+++ b/packages/xo-web/src/common/intl/messages.js
@@ -280,6 +280,7 @@ const messages = {
jobInterrupted: 'Interrupted',
jobStarted: 'Started',
saveBackupJob: 'Save',
+ resetBackupJob: 'Reset',
createBackupJob: 'Create',
deleteBackupSchedule: 'Remove backup job',
deleteBackupScheduleQuestion:
diff --git a/packages/xo-web/src/common/smart-backup-pattern.js b/packages/xo-web/src/common/smart-backup/index.js
similarity index 95%
rename from packages/xo-web/src/common/smart-backup-pattern.js
rename to packages/xo-web/src/common/smart-backup/index.js
index f8f0291c6..8ad11d172 100644
--- a/packages/xo-web/src/common/smart-backup-pattern.js
+++ b/packages/xo-web/src/common/smart-backup/index.js
@@ -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'
diff --git a/packages/xo-web/src/common/smart-backup-preview.js b/packages/xo-web/src/common/smart-backup/preview.js
similarity index 88%
rename from packages/xo-web/src/common/smart-backup-preview.js
rename to packages/xo-web/src/common/smart-backup/preview.js
index f06e1767c..52fa88539 100644
--- a/packages/xo-web/src/common/smart-backup-preview.js
+++ b/packages/xo-web/src/common/smart-backup/preview.js
@@ -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
diff --git a/packages/xo-web/src/xo-app/backup-ng/index.js b/packages/xo-web/src/xo-app/backup-ng/index.js
index 6254d1a92..476134c2b 100644
--- a/packages/xo-web/src/xo-app/backup-ng/index.js
+++ b/packages/xo-web/src/xo-app/backup-ng/index.js
@@ -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'
diff --git a/packages/xo-web/src/xo-app/backup-ng/new/index.js b/packages/xo-web/src/xo-app/backup-ng/new/index.js
index 2e60507d3..bf8b79f43 100644
--- a/packages/xo-web/src/xo-app/backup-ng/new/index.js
+++ b/packages/xo-web/src/xo-app/backup-ng/new/index.js
@@ -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 }) => (
-
-
-
-
-
-
{_('editBackupSmartPools')}
-
-
-
-
-
-
-
-
-
- {_('editBackupSmartTags')}
-
-
-
-
-
-
-
-
-
-
-
-))
-
-// ===================================================================
-
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 => (
),
- 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,26 +280,33 @@ export default [
}
},
setVms: (_, vms) => state => ({ ...state, vms }),
- updateParams: () => (state, { job }) => ({
- ...state,
- compression: job.compression === 'native',
- delta: job.mode === 'delta',
- name: job.name,
- paramsUpdated: true,
- smartMode: job.vms.id === undefined,
- snapshotMode:
- some(
- 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) : [],
- ...destructVmsPattern(job.vms),
- }),
+ 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',
+ name: job.name,
+ paramsUpdated: true,
+ smartMode: job.vms.id === undefined,
+ snapshotMode:
+ some(
+ job.settings,
+ ({ snapshotRetention }) => snapshotRetention > 0
+ ) || undefined,
+ 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 [
{map(state.remotes, (id, key) => (
-
- {state.allRemotes &&
+ {state.storedRemotes &&
renderXoItem({
type: 'remote',
- value: state.allRemotes[id],
+ value: state.storedRemotes[id],
})}
- {state.paramsUpdated ? (
-
- {_('scheduleEdit')}
-
- ) : (
-
- {_('createBackupJob')}
-
- )}
+
+
+ {state.paramsUpdated ? (
+
+ {_('scheduleEdit')}
+
+ ) : (
+
+ {_('createBackupJob')}
+
+ )}
+
+ {_('resetBackupJob')}
+
+
+
diff --git a/packages/xo-web/src/xo-app/backup-ng/new/new-schedule.js b/packages/xo-web/src/xo-app/backup-ng/new/new-schedule.js
index cb18ff7c2..57171a938 100644
--- a/packages/xo-web/src/xo-app/backup-ng/new/new-schedule.js
+++ b/packages/xo-web/src/xo-app/backup-ng/new/new-schedule.js
@@ -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,
diff --git a/packages/xo-web/src/xo-app/backup-ng/new/schedules.js b/packages/xo-web/src/xo-app/backup-ng/new/schedules.js
index 9cda2cfb5..27d578bb3 100644
--- a/packages/xo-web/src/xo-app/backup-ng/new/schedules.js
+++ b/packages/xo-web/src/xo-app/backup-ng/new/schedules.js
@@ -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),
diff --git a/packages/xo-web/src/xo-app/backup-ng/new/smart-backup.js b/packages/xo-web/src/xo-app/backup-ng/new/smart-backup.js
new file mode 100644
index 000000000..1515bc52e
--- /dev/null
+++ b/packages/xo-web/src/xo-app/backup-ng/new/smart-backup.js
@@ -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 }) => (
+
+
+
+
+
+
{_('editBackupSmartPools')}
+
+
+
+
+
+
+
+
+
+ {_('editBackupSmartTags')}
+
+
+
+
+
+
+
+
+
+
+
+ ),
+].reduceRight((value, decorator) => decorator(value))
diff --git a/packages/xo-web/src/xo-app/backup/new/index.js b/packages/xo-web/src/xo-app/backup/new/index.js
index 213853260..0c0fbc043 100644
--- a/packages/xo-web/src/xo-app/backup/new/index.js
+++ b/packages/xo-web/src/xo-app/backup/new/index.js
@@ -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'
diff --git a/packages/xo-web/src/xo-app/backup/overview/index.js b/packages/xo-web/src/xo-app/backup/overview/index.js
index e7a0be2d1..5358c3b75 100644
--- a/packages/xo-web/src/xo-app/backup/overview/index.js
+++ b/packages/xo-web/src/xo-app/backup/overview/index.js
@@ -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'