fix(xo-web/backup): smart mode preview should ignore xo:no-bak
tags (#7331)
Introduced by 87a9fbe23
Fixes https://xcp-ng.org/forum/post/69797
This commit is contained in:
parent
f5a3cc0cdb
commit
32dd16114e
@ -42,6 +42,7 @@
|
||||
- [Backup/Restore] Don't count memory as a key (i.e. complete) disk [Forum#8212](https://xcp-ng.org/forum/post/69591) (PR [#7315](https://github.com/vatesfr/xen-orchestra/pull/7315))
|
||||
- [Pool/patches] Disable Rolling Pool Update button if host is alone in its pool [#6415](https://github.com/vatesfr/xen-orchestra/issues/6415) (PR [#7286](https://github.com/vatesfr/xen-orchestra/pull/7286))
|
||||
- [PIF] Fix IPv4 reconfiguration only worked when the IPv4 mode was updated (PR [#7324](https://github.com/vatesfr/xen-orchestra/pull/7324))
|
||||
- [Backup/Smart mode] Make preview correctly ignoring `xo:no-bak` tags [Forum#69797](https://xcp-ng.org/forum/post/69797) (PR [#7331](https://github.com/vatesfr/xen-orchestra/pull/7331))
|
||||
|
||||
### Packages to release
|
||||
|
||||
|
@ -1,50 +0,0 @@
|
||||
import * as CM from 'complex-matcher'
|
||||
import escapeRegExp from 'lodash/escapeRegExp.js'
|
||||
|
||||
const valueToComplexMatcher = pattern => {
|
||||
if (typeof pattern === 'string') {
|
||||
return new CM.RegExpNode(`^${escapeRegExp(pattern)}$`, 'i')
|
||||
}
|
||||
|
||||
if (Array.isArray(pattern)) {
|
||||
return new CM.And(pattern.map(valueToComplexMatcher))
|
||||
}
|
||||
|
||||
if (pattern !== null && typeof pattern === 'object') {
|
||||
const keys = Object.keys(pattern)
|
||||
const { length } = keys
|
||||
|
||||
if (length === 1) {
|
||||
const [key] = keys
|
||||
if (key === '__and') {
|
||||
return new CM.And(pattern.__and.map(valueToComplexMatcher))
|
||||
}
|
||||
if (key === '__or') {
|
||||
return new CM.Or(pattern.__or.map(valueToComplexMatcher))
|
||||
}
|
||||
if (key === '__not') {
|
||||
return new CM.Not(valueToComplexMatcher(pattern.__not))
|
||||
}
|
||||
}
|
||||
|
||||
const children = []
|
||||
Object.keys(pattern).forEach(property => {
|
||||
const subpattern = pattern[property]
|
||||
if (subpattern !== undefined) {
|
||||
children.push(new CM.Property(property, valueToComplexMatcher(subpattern)))
|
||||
}
|
||||
})
|
||||
return children.length === 0 ? new CM.Null() : new CM.And(children)
|
||||
}
|
||||
|
||||
throw new Error('could not transform this pattern')
|
||||
}
|
||||
|
||||
export default pattern => {
|
||||
try {
|
||||
return valueToComplexMatcher(pattern).toString()
|
||||
} catch (error) {
|
||||
console.warn('constructQueryString', pattern, error)
|
||||
return ''
|
||||
}
|
||||
}
|
@ -1,17 +1,16 @@
|
||||
import _ from 'intl'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import { createPredicate } from 'value-matcher'
|
||||
import { createSelector } from 'reselect'
|
||||
import { filter, map, pickBy } from 'lodash'
|
||||
import { filter, map } from 'lodash'
|
||||
|
||||
import Component from './../base-component'
|
||||
import constructQueryString from '../construct-query-string'
|
||||
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 { smartModeToComplexMatcher } from '../smartModeToComplexMatcher'
|
||||
|
||||
const SAMPLE_SIZE_OF_MATCHING_VMS = 3
|
||||
|
||||
@ -21,18 +20,26 @@ export default class SmartBackupPreview extends Component {
|
||||
vms: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
// user pattern completed with support for `xo:no-bak` tag automatically
|
||||
// ignored by xo-server
|
||||
_getComplexMatcher = createSelector(() => this.props.pattern, smartModeToComplexMatcher)
|
||||
|
||||
_getMatchingVms = createSelector(
|
||||
() => this.props.vms,
|
||||
createSelector(
|
||||
() => this.props.pattern,
|
||||
pattern => createPredicate(pickBy(pattern, val => val != null))
|
||||
),
|
||||
createSelector(this._getComplexMatcher, cm => cm.createPredicate()),
|
||||
(vms, predicate) => filter(vms, predicate)
|
||||
)
|
||||
|
||||
_getSampleOfMatchingVms = createSelector(this._getMatchingVms, vms => vms.slice(0, SAMPLE_SIZE_OF_MATCHING_VMS))
|
||||
|
||||
_getQueryString = createSelector(() => this.props.pattern, constructQueryString)
|
||||
_getQueryString = createSelector(this._getComplexMatcher, cm => {
|
||||
try {
|
||||
return cm.toString()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
render() {
|
||||
const nMatchingVms = this._getMatchingVms().length
|
||||
|
60
packages/xo-web/src/common/smartModeToComplexMatcher.js
Normal file
60
packages/xo-web/src/common/smartModeToComplexMatcher.js
Normal file
@ -0,0 +1,60 @@
|
||||
import * as CM from 'complex-matcher'
|
||||
import escapeRegExp from 'lodash/escapeRegExp.js'
|
||||
|
||||
// compile a value-matcher like pattern (plus support for regexps) to a
|
||||
// complex-matcher pattern
|
||||
const pseudoValueToComplexMatcher = pattern => {
|
||||
if (typeof pattern === 'string') {
|
||||
return new CM.RegExpNode(`^${escapeRegExp(pattern)}$`, 'i')
|
||||
}
|
||||
|
||||
if (Array.isArray(pattern)) {
|
||||
return new CM.And(pattern.map(pseudoValueToComplexMatcher))
|
||||
}
|
||||
|
||||
if (pattern instanceof RegExp) {
|
||||
return new CM.RegExpNode(pattern)
|
||||
}
|
||||
|
||||
if (pattern !== null && typeof pattern === 'object') {
|
||||
const keys = Object.keys(pattern)
|
||||
const { length } = keys
|
||||
|
||||
if (length === 1) {
|
||||
const [key] = keys
|
||||
if (key === '__and') {
|
||||
return new CM.And(pattern.__and.map(pseudoValueToComplexMatcher))
|
||||
}
|
||||
if (key === '__or') {
|
||||
return new CM.Or(pattern.__or.map(pseudoValueToComplexMatcher))
|
||||
}
|
||||
if (key === '__not') {
|
||||
return new CM.Not(pseudoValueToComplexMatcher(pattern.__not))
|
||||
}
|
||||
}
|
||||
|
||||
const children = []
|
||||
Object.keys(pattern).forEach(property => {
|
||||
const subpattern = pattern[property]
|
||||
if (subpattern !== undefined) {
|
||||
children.push(new CM.Property(property, pseudoValueToComplexMatcher(subpattern)))
|
||||
}
|
||||
})
|
||||
return children.length === 0 ? new CM.Null() : new CM.And(children)
|
||||
}
|
||||
|
||||
throw new Error('could not transform this pattern')
|
||||
}
|
||||
|
||||
export const smartModeToComplexMatcher = pattern => {
|
||||
// don't mutate param
|
||||
pattern = JSON.parse(JSON.stringify(pattern))
|
||||
|
||||
// if the pattern does not match expected entries, simply don't change it
|
||||
const { tags } = pattern
|
||||
if (tags !== undefined) {
|
||||
;(tags.__not ?? tags.__and?.[1]?.__not)?.__or?.push(/^xo:no-bak(?:=.*)?$/)
|
||||
}
|
||||
|
||||
return pseudoValueToComplexMatcher(pattern)
|
||||
}
|
@ -3,12 +3,12 @@ import React from 'react'
|
||||
|
||||
import _ from './intl'
|
||||
import Component from './base-component'
|
||||
import constructQueryString from './construct-query-string'
|
||||
import Icon from './icon'
|
||||
import Link from './link'
|
||||
import Tooltip from './tooltip'
|
||||
import { connectStore } from './utils'
|
||||
import { createCollectionWrapper, createGetObjectsOfType, createSelector } from './selectors'
|
||||
import { smartModeToComplexMatcher } from './smartModeToComplexMatcher'
|
||||
|
||||
@connectStore({
|
||||
containers: createSelector(createGetObjectsOfType('pool'), createGetObjectsOfType('host'), (pools, hosts) => ({
|
||||
@ -41,11 +41,11 @@ export default class ZstdChecker extends Component {
|
||||
pathname: '/home',
|
||||
query: {
|
||||
t: 'VM',
|
||||
s: constructQueryString({
|
||||
s: smartModeToComplexMatcher({
|
||||
id: {
|
||||
__or: vms,
|
||||
},
|
||||
}),
|
||||
}).toString(),
|
||||
},
|
||||
}))
|
||||
|
||||
|
@ -3,7 +3,6 @@ import _ from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import addSubscriptions from 'add-subscriptions'
|
||||
import Button from 'button'
|
||||
import constructQueryString from 'construct-query-string'
|
||||
import Copiable from 'copiable'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
import decorate from 'apply-decorators'
|
||||
@ -21,6 +20,7 @@ import { get } from '@xen-orchestra/defined'
|
||||
import { groupBy, isEmpty, map, some } from 'lodash'
|
||||
import { injectState, provideState } from 'reaclette'
|
||||
import { Proxy } from 'render-xo-item'
|
||||
import { smartModeToComplexMatcher } from 'smartModeToComplexMatcher'
|
||||
import { withRouter } from 'react-router'
|
||||
import {
|
||||
cancelJob,
|
||||
@ -360,7 +360,7 @@ class JobsTable extends React.Component {
|
||||
handler: (job, { goTo }) =>
|
||||
goTo({
|
||||
pathname: '/home',
|
||||
query: { t: 'VM', s: constructQueryString(job.vms) },
|
||||
query: { t: 'VM', s: smartModeToComplexMatcher(job.vms).toString() },
|
||||
}),
|
||||
disabled: job => job.type !== 'backup',
|
||||
label: _('redirectToMatchingVms'),
|
||||
|
Loading…
Reference in New Issue
Block a user