chore: format with Prettier

Introduced by 059843f03
This commit is contained in:
Julien Fontanet 2021-06-03 12:12:59 +02:00
parent ec4dde86f5
commit 6d39512576
64 changed files with 1108 additions and 863 deletions

View File

@ -17,10 +17,10 @@ interface Record {
} }
export class AuditCore { export class AuditCore {
constructor(storage: Storage) { } constructor(storage: Storage) {}
public add(subject: any, event: string, data: any): Promise<Record> { } public add(subject: any, event: string, data: any): Promise<Record> {}
public checkIntegrity(oldest: string, newest: string): Promise<number> { } public checkIntegrity(oldest: string, newest: string): Promise<number> {}
public getFrom(newest?: string): AsyncIterator { } public getFrom(newest?: string): AsyncIterator {}
public deleteFrom(newest: string): Promise<void> { } public deleteFrom(newest: string): Promise<void> {}
public deleteRangeAndRewrite(newest: string, oldest: string): Promise<void> { } public deleteRangeAndRewrite(newest: string, oldest: string): Promise<void> {}
} }

View File

@ -7,23 +7,25 @@ const { execFile } = require('child_process')
const parse = createParser({ const parse = createParser({
keyTransform: key => key.slice(5).toLowerCase(), keyTransform: key => key.slice(5).toLowerCase(),
}) })
const makeFunction = command => async (fields, ...args) => { const makeFunction =
const info = await fromCallback(execFile, command, [ command =>
'--noheading', async (fields, ...args) => {
'--nosuffix', const info = await fromCallback(execFile, command, [
'--nameprefixes', '--noheading',
'--unbuffered', '--nosuffix',
'--units', '--nameprefixes',
'b', '--unbuffered',
'-o', '--units',
String(fields), 'b',
...args, '-o',
]) String(fields),
return info ...args,
.trim() ])
.split(/\r?\n/) return info
.map(Array.isArray(fields) ? parse : line => parse(line)[fields]) .trim()
} .split(/\r?\n/)
.map(Array.isArray(fields) ? parse : line => parse(line)[fields])
}
exports.lvs = makeFunction('lvs') exports.lvs = makeFunction('lvs')
exports.pvs = makeFunction('pvs') exports.pvs = makeFunction('pvs')

View File

@ -20,36 +20,8 @@ if (process.stdout !== undefined && process.stdout.isTTY && process.stderr !== u
} }
const NAMESPACE_COLORS = [ const NAMESPACE_COLORS = [
196, 196, 202, 208, 214, 220, 226, 190, 154, 118, 82, 46, 47, 48, 49, 50, 51, 45, 39, 33, 27, 21, 57, 93, 129, 165, 201,
202, 200, 199, 198, 197,
208,
214,
220,
226,
190,
154,
118,
82,
46,
47,
48,
49,
50,
51,
45,
39,
33,
27,
21,
57,
93,
129,
165,
201,
200,
199,
198,
197,
] ]
formatNamespace = namespace => { formatNamespace = namespace => {
// https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ // https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/

View File

@ -28,9 +28,10 @@ export default {
buffer.toString('hex', offset + 5, offset + 6), buffer.toString('hex', offset + 5, offset + 6),
stringToEth: (string, buffer, offset) => { stringToEth: (string, buffer, offset) => {
const eth = /^([0-9A-Fa-f]{2}):([0-9A-Fa-f]{2}):([0-9A-Fa-f]{2}):([0-9A-Fa-f]{2}):([0-9A-Fa-f]{2}):([0-9A-Fa-f]{2})$/.exec( const eth =
string /^([0-9A-Fa-f]{2}):([0-9A-Fa-f]{2}):([0-9A-Fa-f]{2}):([0-9A-Fa-f]{2}):([0-9A-Fa-f]{2}):([0-9A-Fa-f]{2})$/.exec(
) string
)
assert(eth !== null) assert(eth !== null)
buffer.writeUInt8(parseInt(eth[1], 16), offset) buffer.writeUInt8(parseInt(eth[1], 16), offset)
buffer.writeUInt8(parseInt(eth[2], 16), offset + 1) buffer.writeUInt8(parseInt(eth[2], 16), offset + 1)
@ -50,9 +51,10 @@ export default {
), ),
stringToip4: (string, buffer, offset) => { stringToip4: (string, buffer, offset) => {
const ip = /^([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])$/.exec( const ip =
string /^([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])$/.exec(
) string
)
assert(ip !== null) assert(ip !== null)
buffer.writeUInt8(parseInt(ip[1], 10), offset) buffer.writeUInt8(parseInt(ip[1], 10), offset)
buffer.writeUInt8(parseInt(ip[2], 10), offset + 1) buffer.writeUInt8(parseInt(ip[2], 10), offset + 1)

View File

@ -36,7 +36,14 @@ async function main(argv) {
const { hostname = 'localhost', port } = config?.http?.listen?.https ?? {} const { hostname = 'localhost', port } = config?.http?.listen?.https ?? {}
const { _: args, file, help, host, raw, token } = getopts(argv, { const {
_: args,
file,
help,
host,
raw,
token,
} = getopts(argv, {
alias: { file: 'f', help: 'h' }, alias: { file: 'f', help: 'h' },
boolean: ['help', 'raw'], boolean: ['help', 'raw'],
default: { default: {

View File

@ -93,10 +93,7 @@ declare namespace event {
snapshot: Task snapshot: Task
} }
function from(_: { function from(_: { token: string = ''; timeout?: number }): {
token: string = ''
timeout?: number
}): {
events: Event[] events: Event[]
token: string token: string
} }
@ -146,13 +143,13 @@ declare namespace backup {
streamLogs: boolean = false streamLogs: boolean = false
}): string }): string
function listPoolMetadataBackups(_: { function listPoolMetadataBackups(_: { remotes: { [id: string]: Remote } }): {
remotes: { [id: string]: Remote } [remoteId: string]: { [poolUuid: string]: object[] }
}): { [remoteId: string]: { [poolUuid: string]: object[] } } }
function listVmBackups(_: { function listVmBackups(_: { remotes: { [remoteId: string]: Remote } }): {
remotes: { [remoteId: string]: Remote } [remoteId: string]: { [vmUuid: string]: object[] }
}): { [remoteId: string]: { [vmUuid: string]: object[] } } }
function listXoMetadataBackups(_: { remotes: { [id: string]: Remote } }): { [remoteId: string]: object[] } function listXoMetadataBackups(_: { remotes: { [id: string]: Remote } }): { [remoteId: string]: object[] }

View File

@ -320,6 +320,7 @@ You can learn more about XenServer [resource management on the Citrix Website](h
:::tip :::tip
XCP-ng doesn't limit VMs to 32 vCPU XCP-ng doesn't limit VMs to 32 vCPU
::: :::
### VDI live migration ### VDI live migration
Thanks to Xen Storage Motion, it's easy to move a VM disk from one storage location to another, while the VM is running! This feature can help you migrate from your local storage to a SAN, or just upgrade your SAN without any downtime. Thanks to Xen Storage Motion, it's easy to move a VM disk from one storage location to another, while the VM is running! This feature can help you migrate from your local storage to a SAN, or just upgrade your SAN without any downtime.
@ -491,10 +492,12 @@ If you are behind a proxy, please update your `xo-server` configuration to add a
::: danger ::: danger
As specified in the [documentation](https://xcp-ng.org/docs/requirements.html#pool-requirements) your pool shouldn't consist of hosts from different CPU vendors. As specified in the [documentation](https://xcp-ng.org/docs/requirements.html#pool-requirements) your pool shouldn't consist of hosts from different CPU vendors.
::: :::
::: warning ::: warning
- Even with matching CPU vendors, in the case of different CPU models XCP-ng will scale the pool CPU ability to the CPU having the least instructions. - Even with matching CPU vendors, in the case of different CPU models XCP-ng will scale the pool CPU ability to the CPU having the least instructions.
- All the hosts in a pool must run the same XCP-ng version. - All the hosts in a pool must run the same XCP-ng version.
::: :::
### Creating a pool ### Creating a pool
First you should add your new host to XOA by going to New > Server as described in [the relevant chapter](manage_infrastructure.md#add-a-host). First you should add your new host to XOA by going to New > Server as described in [the relevant chapter](manage_infrastructure.md#add-a-host).

View File

@ -59,9 +59,11 @@ While creating a standard backup job from your main Xen Orchestra appliance, you
Login is disabled by default on proxy appliances. Login is disabled by default on proxy appliances.
If you need to login for some reason, you need to set a password for the xoa user via the XenStore of the VM. The following is to be ran on your XCP-ng host: If you need to login for some reason, you need to set a password for the xoa user via the XenStore of the VM. The following is to be ran on your XCP-ng host:
``` ```
xe vm-param-set uuid=<UUID> xenstore-data:vm-data/system-account-xoa-password=<password> xe vm-param-set uuid=<UUID> xenstore-data:vm-data/system-account-xoa-password=<password>
``` ```
Where UUID is the uuid of your proxy VM. Where UUID is the uuid of your proxy VM.
Then you need to restart the proxy VM. Then you need to restart the proxy VM.
@ -74,15 +76,19 @@ First you will need to add a second VIF to your Proxy VM. This can be done in th
After adding the VIF you will need to set an IP for the new NIC, for that you will first need to SSH to the VM [as describe before](/proxy.md#enabling-login-to-proxy-appliance). After adding the VIF you will need to set an IP for the new NIC, for that you will first need to SSH to the VM [as describe before](/proxy.md#enabling-login-to-proxy-appliance).
Then set the new IP: Then set the new IP:
``` ```
$ xoa network static eth1 $ xoa network static eth1
? Static IP for this machine 192.168.100.120 ? Static IP for this machine 192.168.100.120
? Network mask (eg 255.255.255.0) 255.255.255.0 ? Network mask (eg 255.255.255.0) 255.255.255.0
``` ```
If you want to set a static address. If you want to set a static address.
``` ```
$ xoa network dhcp eth1 $ xoa network dhcp eth1
``` ```
If you prefer using DHCP. If you prefer using DHCP.
:::tip :::tip
As XOA uses the first IP address reported by XAPI to contact the proxy appliance, you may have to switch the network card order if you want your proxy to be connected through a specific IP address. As XOA uses the first IP address reported by XAPI to contact the proxy appliance, you may have to switch the network card order if you want your proxy to be connected through a specific IP address.

View File

@ -19,9 +19,11 @@ XOA uses HVM mode. If your physical host doesn't support virtualization extensio
## Set or recover XOA VM password ## Set or recover XOA VM password
As no password is set for the xoa system user by default, you will need to set your own. This can be done via the XenStore data of the VM. The following is to be ran on your XCP-ng host: As no password is set for the xoa system user by default, you will need to set your own. This can be done via the XenStore data of the VM. The following is to be ran on your XCP-ng host:
``` ```
xe vm-param-set uuid=<UUID> xenstore-data:vm-data/system-account-xoa-password=<password> xe vm-param-set uuid=<UUID> xenstore-data:vm-data/system-account-xoa-password=<password>
``` ```
Where UUID is the uuid of your XOA VM. Where UUID is the uuid of your XOA VM.
Then you need to restart the VM. Then you need to restart the VM.

View File

@ -15,24 +15,28 @@ const authorized = () => true // eslint-disable-line no-unused-vars
const forbiddden = () => false // eslint-disable-line no-unused-vars const forbiddden = () => false // eslint-disable-line no-unused-vars
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const and = (...checkers) => (object, permission) => { const and =
for (const checker of checkers) { (...checkers) =>
if (!checker(object, permission)) { (object, permission) => {
return false for (const checker of checkers) {
if (!checker(object, permission)) {
return false
}
} }
return true
} }
return true
}
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const or = (...checkers) => (object, permission) => { const or =
for (const checker of checkers) { (...checkers) =>
if (checker(object, permission)) { (object, permission) => {
return true for (const checker of checkers) {
if (checker(object, permission)) {
return true
}
} }
return false
} }
return false
}
// ------------------------------------------------------------------- // -------------------------------------------------------------------

View File

@ -7,10 +7,7 @@ import execPromise = require('exec-promise')
import through2 = require('through2') import through2 = require('through2')
import Xo from 'xo-lib' import Xo from 'xo-lib'
const parseBoolean = ( const parseBoolean = (value: string, defaultValue?: boolean): boolean | undefined => {
value: string,
defaultValue?: boolean
): boolean | undefined => {
if (value === undefined || value === '') { if (value === undefined || value === '') {
return defaultValue return defaultValue
} }
@ -49,30 +46,24 @@ execPromise(
const errors: any[] = [] const errors: any[] = []
const stream = process.stdin.pipe(csvParser()).pipe( const stream = process.stdin.pipe(csvParser()).pipe(
through2.obj( through2.obj(({ allowUnauthorized, autoConnect, host, label, password, username }, _, next) => {
( console.log('server', host)
{ allowUnauthorized, autoConnect, host, label, password, username },
_,
next
) => {
console.log('server', host)
xo.call('server.add', { xo.call('server.add', {
allowUnauthorized: parseBoolean(allowUnauthorized), allowUnauthorized: parseBoolean(allowUnauthorized),
autoConnect: parseBoolean(autoConnect, false), autoConnect: parseBoolean(autoConnect, false),
host, host,
label, label,
password, password,
username, username,
}).then( }).then(
() => next(), () => next(),
(error: any) => { (error: any) => {
errors.push({ host, error }) errors.push({ host, error })
return next() return next()
} }
) )
} })
)
) )
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {

View File

@ -445,18 +445,14 @@ class Netbox {
this.#makeRequest('/virtualization/interfaces/', 'DELETE', interfacesToDelete), this.#makeRequest('/virtualization/interfaces/', 'DELETE', interfacesToDelete),
isEmpty(interfacesToCreateByVif) isEmpty(interfacesToCreateByVif)
? {} ? {}
: this.#makeRequest( : this.#makeRequest('/virtualization/interfaces/', 'POST', Object.values(interfacesToCreateByVif)).then(
'/virtualization/interfaces/', interfaces => zipObject(Object.keys(interfacesToCreateByVif), interfaces)
'POST', ),
Object.values(interfacesToCreateByVif)
).then(interfaces => zipObject(Object.keys(interfacesToCreateByVif), interfaces)),
isEmpty(interfacesToUpdateByVif) isEmpty(interfacesToUpdateByVif)
? {} ? {}
: this.#makeRequest( : this.#makeRequest('/virtualization/interfaces/', 'PATCH', Object.values(interfacesToUpdateByVif)).then(
'/virtualization/interfaces/', interfaces => zipObject(Object.keys(interfacesToUpdateByVif), interfaces)
'PATCH', ),
Object.values(interfacesToUpdateByVif)
).then(interfaces => zipObject(Object.keys(interfacesToUpdateByVif), interfaces)),
]) ])
) )
.slice(1) .slice(1)

View File

@ -24,7 +24,10 @@ getMethodsInfo.permission = null // user does not need to be authenticated
// ------------------------------------------------------------------- // -------------------------------------------------------------------
export const getServerTimezone = (tz => () => tz)(moment.tz.guess()) export const getServerTimezone = (
tz => () =>
tz
)(moment.tz.guess())
getServerTimezone.description = 'return the timezone server' getServerTimezone.description = 'return the timezone server'
// ------------------------------------------------------------------- // -------------------------------------------------------------------

View File

@ -8,7 +8,7 @@ export default function globMatcher(patterns, opts) {
if (!Array.isArray(patterns)) { if (!Array.isArray(patterns)) {
if (patterns[0] === '!') { if (patterns[0] === '!') {
const m = matcher(patterns.slice(1), opts) const m = matcher(patterns.slice(1), opts)
return function(string) { return function (string) {
return !m(string) return !m(string)
} }
} else { } else {
@ -32,7 +32,7 @@ export default function globMatcher(patterns, opts) {
const nNone = noneMustMatch.length const nNone = noneMustMatch.length
const nAny = anyMustMatch.length const nAny = anyMustMatch.length
return function(string) { return function (string) {
if (typeof string !== 'string') { if (typeof string !== 'string') {
return false return false
} }

View File

@ -40,7 +40,9 @@ export const mergeObjects = objects => Object.assign({}, ...objects)
// //
// Ex: crossProduct([ [ { a: 2 }, { b: 3 } ], [ { c: 5 }, { d: 7 } ] ] ) // Ex: crossProduct([ [ { a: 2 }, { b: 3 } ], [ { c: 5 }, { d: 7 } ] ] )
// => [ { a: 2, c: 5 }, { b: 3, c: 5 }, { a: 2, d: 7 }, { b: 3, d: 7 } ] // => [ { a: 2, c: 5 }, { b: 3, c: 5 }, { a: 2, d: 7 }, { b: 3, d: 7 } ]
export const crossProduct = (vectors, mergeFn = mergeObjects) => cb => export const crossProduct =
combine(vectors)(vector => { (vectors, mergeFn = mergeObjects) =>
cb(mergeFn(vector)) cb =>
}) combine(vectors)(vector => {
cb(mergeFn(vector))
})

View File

@ -99,13 +99,16 @@ export default class Xapi extends XapiBase {
this._snapshotVm = limitConcurrency(vmSnapshotConcurrency)(this._snapshotVm) this._snapshotVm = limitConcurrency(vmSnapshotConcurrency)(this._snapshotVm)
// Patch getObject to resolve _xapiId property. // Patch getObject to resolve _xapiId property.
this.getObject = (getObject => (...args) => { this.getObject = (
let tmp getObject =>
if ((tmp = args[0]) != null && (tmp = tmp._xapiId) != null) { (...args) => {
args[0] = tmp let tmp
if ((tmp = args[0]) != null && (tmp = tmp._xapiId) != null) {
args[0] = tmp
}
return getObject.apply(this, args)
} }
return getObject.apply(this, args) )(this.getObject)
})(this.getObject)
} }
// Wait for an object to be in a given state. // Wait for an object to be in a given state.

View File

@ -68,7 +68,9 @@ export default class IpPools {
if (await store.has(id)) { if (await store.has(id)) {
await Promise.all( await Promise.all(
(await this._app.getAllResourceSets()).map(async set => { (
await this._app.getAllResourceSets()
).map(async set => {
await this._app.removeLimitFromResourceSet(`ipPool:${id}`, set.id) await this._app.removeLimitFromResourceSet(`ipPool:${id}`, set.id)
return this._app.removeIpPoolFromResourceSet(id, set.id) return this._app.removeIpPoolFromResourceSet(id, set.id)
}) })

View File

@ -177,7 +177,9 @@ export default class {
await Promise.all( await Promise.all(
difference(set.subjects, subjects).map(async subjectId => difference(set.subjects, subjects).map(async subjectId =>
Promise.all( Promise.all(
(await this._app.getAclsForSubject(subjectId)).map(async acl => { (
await this._app.getAclsForSubject(subjectId)
).map(async acl => {
try { try {
const object = this._app.getObject(acl.object) const object = this._app.getObject(acl.object)
if ((object.type === 'VM' || object.type === 'VM-snapshot') && object.resourceSet === id) { if ((object.type === 'VM' || object.type === 'VM-snapshot') && object.resourceSet === id) {

View File

@ -131,50 +131,56 @@ const CustomFields = decorate([
}), }),
provideState({ provideState({
effects: { effects: {
addCustomField: () => (state, { object: { id } }) => { addCustomField:
const dateTimeUtc = moment.utc() () =>
return form({ (state, { object: { id } }) => {
component: CustomFieldModal, const dateTimeUtc = moment.utc()
defaultValue: { return form({
date: dateTimeUtc.format(DATE_FORMAT), component: CustomFieldModal,
isDate: false, defaultValue: {
name: '', date: dateTimeUtc.format(DATE_FORMAT),
text: '', isDate: false,
time: dateTimeUtc.format(TIME_FORMAT), name: '',
}, text: '',
header: ( time: dateTimeUtc.format(TIME_FORMAT),
<span> },
<Icon icon='add' /> {_('addCustomField')} header: (
</span> <span>
), <Icon icon='add' /> {_('addCustomField')}
}).then(params => checkParamsAndCallMethod(addCustomField, id, params)) </span>
}, ),
removeCustomField: (_, { currentTarget: { dataset } }) => (_, { object: { id } }) => }).then(params => checkParamsAndCallMethod(addCustomField, id, params))
removeCustomField(id, dataset.name), },
setCustomField: (effects, { name, value }) => (state, { object: { id } }) => { removeCustomField:
const isDate = PATTERN_DATE_TIME_UTC.test(value) (_, { currentTarget: { dataset } }) =>
const dateTimeUtc = isDate ? moment(value).utc() : undefined (_, { object: { id } }) =>
return form({ removeCustomField(id, dataset.name),
render: props => <CustomFieldModal {...props} update />, setCustomField:
defaultValue: isDate (effects, { name, value }) =>
? { (state, { object: { id } }) => {
date: dateTimeUtc.format(DATE_FORMAT), const isDate = PATTERN_DATE_TIME_UTC.test(value)
isDate, const dateTimeUtc = isDate ? moment(value).utc() : undefined
name, return form({
time: dateTimeUtc.format(TIME_FORMAT), render: props => <CustomFieldModal {...props} update />,
} defaultValue: isDate
: { ? {
isDate, date: dateTimeUtc.format(DATE_FORMAT),
name, isDate,
text: value, name,
}, time: dateTimeUtc.format(TIME_FORMAT),
header: ( }
<span> : {
<Icon icon='edit' /> {_('editCustomField')} isDate,
</span> name,
), text: value,
}).then(params => checkParamsAndCallMethod(setCustomField, id, params)) },
}, header: (
<span>
<Icon icon='edit' /> {_('editCustomField')}
</span>
),
}).then(params => checkParamsAndCallMethod(setCustomField, id, params))
},
}, },
computed: { computed: {
customFields: (_, { object }) => customFields: (_, { object }) =>

View File

@ -4,13 +4,17 @@ export const generateId = () => `i${Math.random().toString(36).slice(2)}`
// TODO: remove these functions once the PR: https://github.com/JsCommunity/reaclette/pull/5 has been merged // TODO: remove these functions once the PR: https://github.com/JsCommunity/reaclette/pull/5 has been merged
// It only supports native inputs // It only supports native inputs
export const linkState = (_, { target }) => () => ({ export const linkState =
[target.name]: (_, { target }) =>
target.nodeName.toLowerCase() === 'input' && target.type.toLowerCase() === 'checkbox' () => ({
? target.checked [target.name]:
: target.value, target.nodeName.toLowerCase() === 'input' && target.type.toLowerCase() === 'checkbox'
}) ? target.checked
: target.value,
})
export const toggleState = (_, { currentTarget: { name } }) => state => ({ export const toggleState =
[name]: !state[name], (_, { currentTarget: { name } }) =>
}) state => ({
[name]: !state[name],
})

View File

@ -635,7 +635,10 @@ const renderXoItem = (item, { className, type: xoType, ...props } = {}) => {
export { renderXoItem as default } export { renderXoItem as default }
export const getRenderXoItemOfType = type => (item, options = {}) => renderXoItem(item, { ...options, type }) export const getRenderXoItemOfType =
type =>
(item, options = {}) =>
renderXoItem(item, { ...options, type })
const GenericXoItem = connectStore(() => { const GenericXoItem = connectStore(() => {
const getObject = createGetObject() const getObject = createGetObject()

View File

@ -193,23 +193,27 @@ class ToggleTd extends Component {
const TableSelect = decorate([ const TableSelect = decorate([
provideState({ provideState({
effects: { effects: {
onChange: (_, tdId, add) => (_, { value, onChange, options }) => { onChange:
let newValue = [...value] (_, tdId, add) =>
const index = sortedIndex(newValue, tdId) (_, { value, onChange, options }) => {
if (add) { let newValue = [...value]
newValue[index] !== tdId && newValue.splice(index, 0, tdId) const index = sortedIndex(newValue, tdId)
} else if (newValue[index] === tdId) { if (add) {
if (newValue.length > 1) { newValue[index] !== tdId && newValue.splice(index, 0, tdId)
newValue.splice(index, 1) } else if (newValue[index] === tdId) {
} else { if (newValue.length > 1) {
newValue = [options[0][0]] newValue.splice(index, 1)
} else {
newValue = [options[0][0]]
}
} }
} onChange(newValue)
onChange(newValue) },
}, selectAll:
selectAll: () => ({ optionsValues }, { onChange }) => { () =>
onChange(optionsValues) ({ optionsValues }, { onChange }) => {
}, onChange(optionsValues)
},
}, },
computed: { computed: {
optionsValues: (_, { options }) => flatten(options), optionsValues: (_, { options }) => flatten(options),
@ -258,15 +262,17 @@ TableSelect.propTypes = {
const TimePicker = decorate([ const TimePicker = decorate([
provideState({ provideState({
effects: { effects: {
onChange: (_, value) => ({ optionsValues }, { onChange }) => { onChange:
if (Array.isArray(value)) { (_, value) =>
value = value.length === optionsValues.length ? '*' : value.join(',') ({ optionsValues }, { onChange }) => {
} else { if (Array.isArray(value)) {
value = `*/${value}` value = value.length === optionsValues.length ? '*' : value.join(',')
} } else {
value = `*/${value}`
}
onChange(value) onChange(value)
}, },
}, },
computed: { computed: {
maxStep: ({ optionsValues }) => Math.floor(optionsValues.length / 2), maxStep: ({ optionsValues }) => Math.floor(optionsValues.length / 2),

View File

@ -524,13 +524,15 @@ export const SelectTag = decorate([
editing: false, editing: false,
}), }),
effects: { effects: {
addTag: (effects, newTag) => ({ value }, { multi, onChange }) => { addTag:
if (newTag === value || (multi && includes(value, newTag))) { (effects, newTag) =>
return ({ value }, { multi, onChange }) => {
} if (newTag === value || (multi && includes(value, newTag))) {
const _newTag = { id: newTag, type: 'tag', value: newTag } return
onChange(multi ? [...map(value, tag => ({ id: tag, type: 'tag', value: tag })), _newTag] : _newTag) }
}, const _newTag = { id: newTag, type: 'tag', value: newTag }
onChange(multi ? [...map(value, tag => ({ id: tag, type: 'tag', value: tag })), _newTag] : _newTag)
},
closeEdition: () => ({ editing: false }), closeEdition: () => ({ editing: false }),
toggleState, toggleState,
}, },

View File

@ -287,31 +287,33 @@ export const isAdmin = (...args) => {
// Common selector creators. // Common selector creators.
// Creates an object selector from an id selector. // Creates an object selector from an id selector.
export const createGetObject = (idSelector = _getId) => (state, props, useResourceSet) => { export const createGetObject =
const object = state.objects.all[idSelector(state, props)] (idSelector = _getId) =>
if (!object) { (state, props, useResourceSet) => {
return const object = state.objects.all[idSelector(state, props)]
} if (!object) {
return
if (useResourceSet) {
return object
}
const predicate = _getPermissionsPredicate(state)
if (!predicate) {
if (predicate == null) {
return object // no filtering
} }
// predicate is false. if (useResourceSet) {
return return object
} }
if (predicate(object)) { const predicate = _getPermissionsPredicate(state)
return object
if (!predicate) {
if (predicate == null) {
return object // no filtering
}
// predicate is false.
return
}
if (predicate(object)) {
return object
}
} }
}
// Specialized createSort() configured for a given type. // Specialized createSort() configured for a given type.
export const createSortForType = invoke(() => { export const createSortForType = invoke(() => {
@ -462,9 +464,12 @@ export const createDoesHostNeedRestart = hostSelector => {
const patchRequiresReboot = createGetObjectsOfType('patch') const patchRequiresReboot = createGetObjectsOfType('patch')
.pick(create(hostSelector, host => host.patches)) .pick(create(hostSelector, host => host.patches))
.find( .find(
create(hostSelector, host => ({ guidance, time, upgrade }) => create(
time > host.startTime && hostSelector,
(upgrade || some(guidance, action => action === 'restartHost' || action === 'restartXapi')) host =>
({ guidance, time, upgrade }) =>
time > host.startTime &&
(upgrade || some(guidance, action => action === 'restartHost' || action === 'restartXapi'))
) )
) )

View File

@ -140,7 +140,10 @@ const Action = decorate([
computed: { computed: {
disabled: ({ items }, { disabled, userData }) => disabled: ({ items }, { disabled, userData }) =>
typeof disabled === 'function' ? disabled(items, userData) : disabled, typeof disabled === 'function' ? disabled(items, userData) : disabled,
handler: ({ items }, { handler, userData }) => () => handler(items, userData), handler:
({ items }, { handler, userData }) =>
() =>
handler(items, userData),
icon: ({ items }, { icon, userData }) => (typeof icon === 'function' ? icon(items, userData) : icon), icon: ({ items }, { icon, userData }) => (typeof icon === 'function' ? icon(items, userData) : icon),
items: (_, { items, grouped }) => (Array.isArray(items) || !grouped ? items : [items]), items: (_, { items, grouped }) => (Array.isArray(items) || !grouped ? items : [items]),
label: ({ items }, { label, userData }) => (typeof label === 'function' ? label(items, userData) : label), label: ({ items }, { label, userData }) => (typeof label === 'function' ? label(items, userData) : label),

View File

@ -7,19 +7,21 @@ import * as actions from './actions'
// =================================================================== // ===================================================================
const createAsyncHandler = ({ error, next }) => (state, payload, action) => { const createAsyncHandler =
if (action.error) { ({ error, next }) =>
if (error) { (state, payload, action) => {
return error(state, payload, action) if (action.error) {
if (error) {
return error(state, payload, action)
}
} else {
if (next) {
return next(state, payload, action)
}
} }
} else {
if (next) {
return next(state, payload, action)
}
}
return state return state
} }
// Action handlers are reducers but bound to a specific action. // Action handlers are reducers but bound to a specific action.
const combineActionHandlers = invoke( const combineActionHandlers = invoke(

View File

@ -576,18 +576,20 @@ export const downloadLog = ({ log, date, type }) => {
// ]) // ])
// ) // )
// ``` // ```
export const createCompare = criterias => (...items) => { export const createCompare =
let res = 0 criterias =>
// Array.find to stop when the result is != 0 (...items) => {
criterias.find(fn => { let res = 0
const [v1, v2] = items.map(item => { // Array.find to stop when the result is != 0
const v = typeof fn === 'string' ? item[fn] : fn(item) criterias.find(fn => {
return v === true ? -1 : v === false ? 1 : v const [v1, v2] = items.map(item => {
const v = typeof fn === 'string' ? item[fn] : fn(item)
return v === true ? -1 : v === false ? 1 : v
})
return (res = v1 < v2 ? -1 : v1 > v2 ? 1 : 0)
}) })
return (res = v1 < v2 ? -1 : v1 > v2 ? 1 : 0) return res
}) }
return res
}
// =================================================================== // ===================================================================

View File

@ -1368,15 +1368,17 @@ export const createVms = (args, nameLabels, cloudConfigs) =>
body: _('newVmCreateVmsConfirm', { nbVms: nameLabels.length }), body: _('newVmCreateVmsConfirm', { nbVms: nameLabels.length }),
}).then(() => }).then(() =>
Promise.all( Promise.all(
map(nameLabels, ( map(
name_label, // eslint-disable-line camelcase nameLabels,
i (
) => name_label, // eslint-disable-line camelcase
_call('vm.create', { i
...args, ) =>
name_label, _call('vm.create', {
cloudConfig: get(cloudConfigs, i), ...args,
}) name_label,
cloudConfig: get(cloudConfigs, i),
})
) )
) )
) )

View File

@ -25,7 +25,14 @@ import { ejectCd, isSrWritable, setDefaultSr } from 'xo'
return { return {
pool: getPool, pool: getPool,
poolMaster: createGetObject(createSelector(getPool, ({ master }) => master)), poolMaster: createGetObject(createSelector(getPool, ({ master }) => master)),
vbds: createGetObjectsOfType('VBD').filter(createSelector(getPool, ({ id }) => vbd => vbd.$pool === id)), vbds: createGetObjectsOfType('VBD').filter(
createSelector(
getPool,
({ id }) =>
vbd =>
vbd.$pool === id
)
),
} }
}, },
{ withRef: true } { withRef: true }

View File

@ -70,7 +70,9 @@ export default class MigrateVmModalBody extends BaseComponent {
this._getHostPredicate = createSelector( this._getHostPredicate = createSelector(
() => this.props.vm, () => this.props.vm,
({ $container }) => host => host.id !== $container ({ $container }) =>
host =>
host.id !== $container
) )
this._getSrPredicate = createSelector( this._getSrPredicate = createSelector(

View File

@ -11,7 +11,11 @@ import { Sr } from '../../render-xo-item'
@connectStore( @connectStore(
{ {
srIds: createSelector( srIds: createSelector(
createGetObjectsOfType('PBD').filter((_, { hostIds }) => pbd => hostIds.includes(pbd.host)), createGetObjectsOfType('PBD').filter(
(_, { hostIds }) =>
pbd =>
hostIds.includes(pbd.host)
),
pbds => { pbds => {
const srIds = new Set([]) const srIds = new Set([])
for (const id in pbds) { for (const id in pbds) {

View File

@ -8,7 +8,11 @@ import { isLatestXosanPackInstalled, connectStore, findLatestPack } from 'utils'
@connectStore( @connectStore(
{ {
hosts: createGetObjectsOfType('host').filter((_, { pool }) => host => host.$pool === pool.id), hosts: createGetObjectsOfType('host').filter(
(_, { pool }) =>
host =>
host.$pool === pool.id
),
}, },
{ withRef: true } { withRef: true }
) )

View File

@ -264,17 +264,8 @@ export default class RestoreFileModalBody extends Component {
render() { render() {
const { backups } = this.props const { backups } = this.props
const { const { backup, disk, partition, partitions, path, scanDiskError, listFilesError, scanningFiles, selectedFiles } =
backup, this.state
disk,
partition,
partitions,
path,
scanDiskError,
listFilesError,
scanningFiles,
selectedFiles,
} = this.state
const noPartitions = isEmpty(partitions) const noPartitions = isEmpty(partitions)
const redundantFiles = this._getRedundantFiles() const redundantFiles = this._getRedundantFiles()

View File

@ -93,65 +93,73 @@ const Schedules = decorate([
delete settings[id] delete settings[id]
props.handlerSettings(settings) props.handlerSettings(settings)
}, },
showModal: (effects, { id = generateRandomId(), name, cron, timezone } = DEFAULT_SCHEDULE) => async ( showModal:
state, (effects, { id = generateRandomId(), name, cron, timezone } = DEFAULT_SCHEDULE) =>
props async (state, props) => {
) => { const schedule = get(() => props.schedules[id])
const schedule = get(() => props.schedules[id]) const setting = get(() => props.settings[id])
const setting = get(() => props.settings[id])
const { cron: newCron, name: newName, timezone: newTimezone, ...newSetting } = await form({ const {
defaultValue: setDefaultRetentions({ cron, name, timezone, ...setting }, state.retentions),
render: props => <NewSchedule retentions={state.retentions} {...props} />,
header: (
<span>
<Icon icon='schedule' /> {_('schedule')}
</span>
),
size: 'large',
handler: value => {
if (areRetentionsMissing(value, state.retentions)) {
throw new UserError(_('newScheduleError'), _('retentionNeeded'))
}
return value
},
})
props.handlerSchedules({
...props.schedules,
[id]: {
...schedule,
cron: newCron, cron: newCron,
id,
name: newName, name: newName,
timezone: newTimezone, timezone: newTimezone,
}, ...newSetting
}) } = await form({
props.handlerSettings({ defaultValue: setDefaultRetentions({ cron, name, timezone, ...setting }, state.retentions),
...props.settings, render: props => <NewSchedule retentions={state.retentions} {...props} />,
[id]: { header: (
...setting, <span>
...newSetting, <Icon icon='schedule' /> {_('schedule')}
}, </span>
}) ),
}, size: 'large',
toggleScheduleState: (_, id) => (state, { handlerSchedules, schedules }) => { handler: value => {
const schedule = schedules[id] if (areRetentionsMissing(value, state.retentions)) {
handlerSchedules({ throw new UserError(_('newScheduleError'), _('retentionNeeded'))
...schedules, }
[id]: { return value
...schedule, },
enabled: !schedule.enabled, })
},
}) props.handlerSchedules({
}, ...props.schedules,
[id]: {
...schedule,
cron: newCron,
id,
name: newName,
timezone: newTimezone,
},
})
props.handlerSettings({
...props.settings,
[id]: {
...setting,
...newSetting,
},
})
},
toggleScheduleState:
(_, id) =>
(state, { handlerSchedules, schedules }) => {
const schedule = schedules[id]
handlerSchedules({
...schedules,
[id]: {
...schedule,
enabled: !schedule.enabled,
},
})
},
}, },
computed: { computed: {
columns: (_, { retentions }) => [...COLUMNS, ...retentions.map(({ defaultValue, ...props }) => props)], columns: (_, { retentions }) => [...COLUMNS, ...retentions.map(({ defaultValue, ...props }) => props)],
rowTransform: (_, { settings = {}, retentions }) => schedule => { rowTransform:
schedule = { ...schedule, ...settings[schedule.id] } (_, { settings = {}, retentions }) =>
return setDefaultRetentions(schedule, retentions) schedule => {
}, schedule = { ...schedule, ...settings[schedule.id] }
return setDefaultRetentions(schedule, retentions)
},
}, },
}), }),
injectState, injectState,

View File

@ -14,23 +14,29 @@ import { areRetentionsMissing } from '.'
export default decorate([ export default decorate([
provideState({ provideState({
effects: { effects: {
setSchedule: (_, params) => (_, { value, onChange }) => { setSchedule:
onChange({ (_, params) =>
...value, (_, { value, onChange }) => {
...params, onChange({
}) ...value,
}, ...params,
setCronTimezone: ({ setSchedule }, { cronPattern: cron, timezone }) => () => { })
setSchedule({ },
cron, setCronTimezone:
timezone, ({ setSchedule }, { cronPattern: cron, timezone }) =>
}) () => {
}, setSchedule({
setName: ({ setSchedule }, { target: { value } }) => () => { cron,
setSchedule({ timezone,
name: value.trim() === '' ? null : value, })
}) },
}, setName:
({ setSchedule }, { target: { value } }) =>
() => {
setSchedule({
name: value.trim() === '' ? null : value,
})
},
setRetention({ setSchedule }, value, { name }) { setRetention({ setSchedule }, value, { name }) {
setSchedule({ setSchedule({
[name]: value, [name]: value,

View File

@ -212,11 +212,12 @@ const DeleteOldBackupsFirst = ({ handler, handlerParam, value }) => (
) )
const New = decorate([ const New = decorate([
New => props => ( New => props =>
<Upgrade place='newBackup' required={2}> (
<New {...props} /> <Upgrade place='newBackup' required={2}>
</Upgrade> <New {...props} />
), </Upgrade>
),
connectStore(() => ({ connectStore(() => ({
hostsById: createGetObjectsOfType('host'), hostsById: createGetObjectsOfType('host'),
poolsById: createGetObjectsOfType('pool'), poolsById: createGetObjectsOfType('pool'),
@ -349,14 +350,18 @@ const New = decorate([
vms: state.smartMode ? state.vmsSmartPattern : constructPattern(state.vms), vms: state.smartMode ? state.vmsSmartPattern : constructPattern(state.vms),
}) })
}, },
toggleMode: (_, { mode }) => state => ({ toggleMode:
...state, (_, { mode }) =>
[mode]: !state[mode], state => ({
}), ...state,
setCheckboxValue: (_, { target: { checked, name } }) => state => ({ [mode]: !state[mode],
...state, }),
[name]: checked, setCheckboxValue:
}), (_, { target: { checked, name } }) =>
state => ({
...state,
[name]: checked,
}),
toggleScheduleState: (_, id) => state => ({ toggleScheduleState: (_, id) => state => ({
...state, ...state,
schedules: { schedules: {
@ -367,15 +372,19 @@ const New = decorate([
}, },
}, },
}), }),
setName: (_, { target: { value } }) => state => ({ setName:
...state, (_, { target: { value } }) =>
name: value, state => ({
}), ...state,
setTargetDeleteFirst: (_, id) => ({ propSettings, settings = propSettings }) => ({ name: value,
settings: settings.set(id, { }),
deleteFirst: !settings.getIn([id, 'deleteFirst']), setTargetDeleteFirst:
(_, id) =>
({ propSettings, settings = propSettings }) => ({
settings: settings.set(id, {
deleteFirst: !settings.getIn([id, 'deleteFirst']),
}),
}), }),
}),
addRemote: (_, remote) => state => { addRemote: (_, remote) => state => {
return { return {
...state, ...state,
@ -403,95 +412,103 @@ const New = decorate([
} }
}, },
setVms: (_, vms) => state => ({ ...state, vms }), setVms: (_, vms) => state => ({ ...state, vms }),
updateParams: () => (_, { job, schedules }) => { updateParams:
const remotes = job.remotes !== undefined ? destructPattern(job.remotes) : [] () =>
const srs = job.srs !== undefined ? destructPattern(job.srs) : [] (_, { job, schedules }) => {
const remotes = job.remotes !== undefined ? destructPattern(job.remotes) : []
const srs = job.srs !== undefined ? destructPattern(job.srs) : []
return { return {
name: job.name, name: job.name,
smartMode: job.vms.id === undefined, smartMode: job.vms.id === undefined,
snapshotMode: some(job.settings, ({ snapshotRetention }) => snapshotRetention > 0), snapshotMode: some(job.settings, ({ snapshotRetention }) => snapshotRetention > 0),
backupMode: job.mode === 'full' && !isEmpty(remotes), backupMode: job.mode === 'full' && !isEmpty(remotes),
deltaMode: job.mode === 'delta' && !isEmpty(remotes), deltaMode: job.mode === 'delta' && !isEmpty(remotes),
drMode: job.mode === 'full' && !isEmpty(srs), drMode: job.mode === 'full' && !isEmpty(srs),
crMode: job.mode === 'delta' && !isEmpty(srs), crMode: job.mode === 'delta' && !isEmpty(srs),
remotes, remotes,
srs, srs,
schedules, schedules,
...destructVmsPattern(job.vms), ...destructVmsPattern(job.vms),
} }
},
showScheduleModal: ({ saveSchedule }, storedSchedule = DEFAULT_SCHEDULE) => async (
{ copyMode, exportMode, deltaMode, isDelta, propSettings, settings = propSettings, snapshotMode },
{ intl: { formatMessage } }
) => {
const modes = { copyMode, isDelta, exportMode, snapshotMode }
const schedule = await form({
defaultValue: storedSchedule,
render: props => (
<NewSchedule
missingRetentions={!checkRetentions(props.value, modes)}
modes={modes}
showRetentionWarning={
deltaMode &&
!isRetentionLow(
defined(props.value.fullInterval, settings.getIn(['', 'fullInterval'])),
props.value.exportRetention
)
}
{...props}
/>
),
header: (
<span>
<Icon icon='schedule' /> {_('schedule')}
</span>
),
size: 'large',
handler: value => {
if (!checkRetentions(value, modes)) {
throw new UserError(_('newScheduleError'), _('retentionNeeded'))
}
return value
},
})
saveSchedule({
...schedule,
id: storedSchedule.id || generateRandomId(),
})
},
deleteSchedule: (_, schedule) => ({ schedules: oldSchedules, propSettings, settings = propSettings }) => {
const id = resolveId(schedule)
const schedules = { ...oldSchedules }
delete schedules[id]
return {
schedules,
settings: settings.delete(id),
}
},
saveSchedule: (
_,
{ copyRetention, cron, enabled = true, exportRetention, fullInterval, id, name, snapshotRetention, timezone }
) => ({ propSettings, schedules, settings = propSettings }) => ({
schedules: {
...schedules,
[id]: {
...schedules[id],
cron,
enabled,
id,
name,
timezone,
},
}, },
settings: settings.set(id, { showScheduleModal:
copyRetention, ({ saveSchedule }, storedSchedule = DEFAULT_SCHEDULE) =>
exportRetention, async (
fullInterval, { copyMode, exportMode, deltaMode, isDelta, propSettings, settings = propSettings, snapshotMode },
snapshotRetention, { intl: { formatMessage } }
) => {
const modes = { copyMode, isDelta, exportMode, snapshotMode }
const schedule = await form({
defaultValue: storedSchedule,
render: props => (
<NewSchedule
missingRetentions={!checkRetentions(props.value, modes)}
modes={modes}
showRetentionWarning={
deltaMode &&
!isRetentionLow(
defined(props.value.fullInterval, settings.getIn(['', 'fullInterval'])),
props.value.exportRetention
)
}
{...props}
/>
),
header: (
<span>
<Icon icon='schedule' /> {_('schedule')}
</span>
),
size: 'large',
handler: value => {
if (!checkRetentions(value, modes)) {
throw new UserError(_('newScheduleError'), _('retentionNeeded'))
}
return value
},
})
saveSchedule({
...schedule,
id: storedSchedule.id || generateRandomId(),
})
},
deleteSchedule:
(_, schedule) =>
({ schedules: oldSchedules, propSettings, settings = propSettings }) => {
const id = resolveId(schedule)
const schedules = { ...oldSchedules }
delete schedules[id]
return {
schedules,
settings: settings.delete(id),
}
},
saveSchedule:
(
_,
{ copyRetention, cron, enabled = true, exportRetention, fullInterval, id, name, snapshotRetention, timezone }
) =>
({ propSettings, schedules, settings = propSettings }) => ({
schedules: {
...schedules,
[id]: {
...schedules[id],
cron,
enabled,
id,
name,
timezone,
},
},
settings: settings.set(id, {
copyRetention,
exportRetention,
fullInterval,
snapshotRetention,
}),
}), }),
}),
onVmsPatternChange: (_, _vmsPattern) => ({ onVmsPatternChange: (_, _vmsPattern) => ({
_vmsPattern, _vmsPattern,
}), }),
@ -509,26 +526,32 @@ const New = decorate([
notValues, notValues,
}, },
}), }),
resetJob: ({ updateParams }) => (state, { job }) => { resetJob:
if (job !== undefined) { ({ updateParams }) =>
updateParams() (state, { job }) => {
} if (job !== undefined) {
updateParams()
}
return getInitialState() return getInitialState()
}, },
setCompression: (_, compression) => ({ compression }), setCompression: (_, compression) => ({ compression }),
setProxy(_, id) { setProxy(_, id) {
this.state._proxyId = id this.state._proxyId = id
}, },
toggleDisplayAdvancedSettings: () => ({ displayAdvancedSettings }) => ({ toggleDisplayAdvancedSettings:
_displayAdvancedSettings: !displayAdvancedSettings, () =>
}), ({ displayAdvancedSettings }) => ({
setGlobalSettings: (_, globalSettings) => ({ propSettings, settings = propSettings }) => ({ _displayAdvancedSettings: !displayAdvancedSettings,
settings: settings.update('', setting => ({ }),
...setting, setGlobalSettings:
...globalSettings, (_, globalSettings) =>
})), ({ propSettings, settings = propSettings }) => ({
}), settings: settings.update('', setting => ({
...setting,
...globalSettings,
})),
}),
addReportRecipient({ setGlobalSettings }, value) { addReportRecipient({ setGlobalSettings }, value) {
const { propSettings, settings = propSettings } = this.state const { propSettings, settings = propSettings } = this.state
const reportRecipients = defined(settings.getIn(['', 'reportRecipients']), []) const reportRecipients = defined(settings.getIn(['', 'reportRecipients']), [])
@ -545,31 +568,39 @@ const New = decorate([
reportRecipients: (reportRecipients.splice(key, 1), reportRecipients), reportRecipients: (reportRecipients.splice(key, 1), reportRecipients),
}) })
}, },
setReportWhen: ({ setGlobalSettings }, { value }) => () => { setReportWhen:
setGlobalSettings({ ({ setGlobalSettings }, { value }) =>
reportWhen: value, () => {
}) setGlobalSettings({
}, reportWhen: value,
setConcurrency: ({ setGlobalSettings }, concurrency) => () => { })
setGlobalSettings({ },
concurrency, setConcurrency:
}) ({ setGlobalSettings }, concurrency) =>
}, () => {
setTimeout: ({ setGlobalSettings }, value) => () => { setGlobalSettings({
setGlobalSettings({ concurrency,
timeout: value && value * 3600e3, })
}) },
}, setTimeout:
({ setGlobalSettings }, value) =>
() => {
setGlobalSettings({
timeout: value && value * 3600e3,
})
},
setFullInterval({ setGlobalSettings }, fullInterval) { setFullInterval({ setGlobalSettings }, fullInterval) {
setGlobalSettings({ setGlobalSettings({
fullInterval, fullInterval,
}) })
}, },
setOfflineBackup: ({ setGlobalSettings }, { target: { checked: offlineBackup } }) => () => { setOfflineBackup:
setGlobalSettings({ ({ setGlobalSettings }, { target: { checked: offlineBackup } }) =>
offlineBackup, () => {
}) setGlobalSettings({
}, offlineBackup,
})
},
}, },
computed: { computed: {
compressionId: generateId, compressionId: generateId,
@ -618,11 +649,13 @@ const New = decorate([
...vmsPattern, ...vmsPattern,
tags: constructSmartPattern(tags, normalizeTagValues), tags: constructSmartPattern(tags, normalizeTagValues),
}), }),
vmPredicate: ({ isDelta }, { hostsById, poolsById }) => ({ $container }) => vmPredicate:
!isDelta || ({ isDelta }, { hostsById, poolsById }) =>
canDeltaBackup( ({ $container }) =>
get(() => hostsById[$container].version) || get(() => hostsById[poolsById[$container].master].version) !isDelta ||
), canDeltaBackup(
get(() => hostsById[$container].version) || get(() => hostsById[poolsById[$container].master].version)
),
selectedVmIds: state => resolveIds(state.vms), selectedVmIds: state => resolveIds(state.vms),
showRetentionWarning: ({ deltaMode, propSettings, settings = propSettings, schedules }) => { showRetentionWarning: ({ deltaMode, propSettings, settings = propSettings, schedules }) => {
if (!deltaMode) { if (!deltaMode) {
@ -639,13 +672,18 @@ const New = decorate([
) )
) )
}, },
srPredicate: ({ srs }) => sr => isSrWritable(sr) && !includes(srs, sr.id), srPredicate:
remotePredicate: ({ proxyId, remotes }) => remote => { ({ srs }) =>
if (proxyId === null) { sr =>
proxyId = undefined isSrWritable(sr) && !includes(srs, sr.id),
} remotePredicate:
return !remotes.includes(remote.id) && remote.value.proxy === proxyId ({ proxyId, remotes }) =>
}, remote => {
if (proxyId === null) {
proxyId = undefined
}
return !remotes.includes(remote.id) && remote.value.proxy === proxyId
},
propSettings: (_, { job }) => propSettings: (_, { job }) =>
Map(get(() => job.settings)).map(setting => Map(get(() => job.settings)).map(setting =>
defined(setting.copyRetention, setting.exportRetention, setting.snapshotRetention) defined(setting.copyRetention, setting.exportRetention, setting.snapshotRetention)

View File

@ -72,11 +72,12 @@ const getInitialState = () => ({
}) })
export default decorate([ export default decorate([
New => props => ( New => props =>
<Upgrade place='newMetadataBackup' required={3}> (
<New {...props} /> <Upgrade place='newMetadataBackup' required={3}>
</Upgrade> <New {...props} />
), </Upgrade>
),
addSubscriptions({ addSubscriptions({
remotes: subscribeRemotes, remotes: subscribeRemotes,
}), }),
@ -166,24 +167,30 @@ export default decorate([
setSettings: (_, _settings) => () => ({ setSettings: (_, _settings) => () => ({
_settings, _settings,
}), }),
setGlobalSettings: ({ setSettings }, name, value) => ({ settings = {} }) => { setGlobalSettings:
setSettings({ ({ setSettings }, name, value) =>
...settings, ({ settings = {} }) => {
[GLOBAL_SETTING_KEY]: { setSettings({
...settings[GLOBAL_SETTING_KEY], ...settings,
[name]: value, [GLOBAL_SETTING_KEY]: {
}, ...settings[GLOBAL_SETTING_KEY],
}) [name]: value,
}, },
})
},
setReportWhen({ setGlobalSettings }, { value }) { setReportWhen({ setGlobalSettings }, { value }) {
setGlobalSettings('reportWhen', value) setGlobalSettings('reportWhen', value)
}, },
toggleMode: (_, { mode }) => state => ({ toggleMode:
[`_${mode}`]: !state[mode], (_, { mode }) =>
}), state => ({
addRemote: (_, { id }) => state => ({ [`_${mode}`]: !state[mode],
_remotes: [...state.remotes, id], }),
}), addRemote:
(_, { id }) =>
state => ({
_remotes: [...state.remotes, id],
}),
deleteRemote: (_, key) => state => { deleteRemote: (_, key) => state => {
const _remotes = [...state.remotes] const _remotes = [...state.remotes]
_remotes.splice(key, 1) _remotes.splice(key, 1)
@ -227,12 +234,14 @@ export default decorate([
}) })
), ),
remotes: ({ _remotes }, { job }) => defined(_remotes, () => destructPattern(job.remotes), []), remotes: ({ _remotes }, { job }) => defined(_remotes, () => destructPattern(job.remotes), []),
remotesPredicate: ({ proxyId, remotes }) => remote => { remotesPredicate:
if (proxyId === null) { ({ proxyId, remotes }) =>
proxyId = undefined remote => {
} if (proxyId === null) {
return !remotes.includes(remote.id) && remote.value.proxy === proxyId proxyId = undefined
}, }
return !remotes.includes(remote.id) && remote.value.proxy === proxyId
},
isJobInvalid: state => isJobInvalid: state =>
state.missingModes || state.missingModes ||

View File

@ -20,38 +20,50 @@ const New = decorate([
idInputName: generateId, idInputName: generateId,
}, },
effects: { effects: {
setSchedule: (_, params) => (_, { value, onChange }) => { setSchedule:
onChange({ (_, params) =>
...value, (_, { value, onChange }) => {
...params, onChange({
}) ...value,
}, ...params,
setExportRetention: ({ setSchedule }, exportRetention) => () => { })
setSchedule({ },
exportRetention, setExportRetention:
}) ({ setSchedule }, exportRetention) =>
}, () => {
setCopyRetention: ({ setSchedule }, copyRetention) => () => { setSchedule({
setSchedule({ exportRetention,
copyRetention, })
}) },
}, setCopyRetention:
setSnapshotRetention: ({ setSchedule }, snapshotRetention) => () => { ({ setSchedule }, copyRetention) =>
setSchedule({ () => {
snapshotRetention, setSchedule({
}) copyRetention,
}, })
setCronTimezone: ({ setSchedule }, { cronPattern: cron, timezone }) => () => { },
setSchedule({ setSnapshotRetention:
cron, ({ setSchedule }, snapshotRetention) =>
timezone, () => {
}) setSchedule({
}, snapshotRetention,
setName: ({ setSchedule }, { target: { value } }) => () => { })
setSchedule({ },
name: value.trim() === '' ? null : value, setCronTimezone:
}) ({ setSchedule }, { cronPattern: cron, timezone }) =>
}, () => {
setSchedule({
cron,
timezone,
})
},
setName:
({ setSchedule }, { target: { value } }) =>
() => {
setSchedule({
name: value.trim() === '' ? null : value,
})
},
toggleForceFullBackup({ setSchedule }) { toggleForceFullBackup({ setSchedule }) {
setSchedule({ setSchedule({
fullInterval: this.state.forceFullBackup ? undefined : 1, fullInterval: this.state.forceFullBackup ? undefined : 1,

View File

@ -39,10 +39,12 @@ export default decorate([
level: 'danger', level: 'danger',
}, },
], ],
rowTransform: ({ propSettings, settings = propSettings }) => schedule => ({ rowTransform:
...schedule, ({ propSettings, settings = propSettings }) =>
...settings.get(schedule.id), schedule => ({
}), ...schedule,
...settings.get(schedule.id),
}),
schedulesColumns: (state, { effects: { toggleScheduleState } }) => { schedulesColumns: (state, { effects: { toggleScheduleState } }) => {
const columns = [ const columns = [
{ {

View File

@ -27,28 +27,32 @@ const SmartBackup = decorate([
}), }),
provideState({ provideState({
effects: { effects: {
setPattern: (_, value) => (_, { pattern, onChange }) => { setPattern:
onChange({ (_, value) =>
...pattern, (_, { pattern, onChange }) => {
...value, onChange({
}) ...pattern,
}, ...value,
})
},
setPowerState({ setPattern }, powerState) { setPowerState({ setPattern }, powerState) {
setPattern({ setPattern({
power_state: powerState === 'All' ? undefined : powerState, power_state: powerState === 'All' ? undefined : powerState,
}) })
}, },
setPoolPattern: ({ setPattern }, { values, notValues }) => ({ pools }) => { setPoolPattern:
setPattern({ ({ setPattern }, { values, notValues }) =>
$pool: constructSmartPattern( ({ pools }) => {
{ setPattern({
values: values || pools.values, $pool: constructSmartPattern(
notValues: notValues || pools.notValues, {
}, values: values || pools.values,
resolveIds notValues: notValues || pools.notValues,
), },
}) resolveIds
}, ),
})
},
setPoolValues({ setPoolPattern }, values) { setPoolValues({ setPoolPattern }, values) {
setPoolPattern({ values }) setPoolPattern({ values })
}, },
@ -57,8 +61,10 @@ const SmartBackup = decorate([
}, },
}, },
computed: { computed: {
poolPredicate: (_, { deltaMode, hosts }) => pool => poolPredicate:
!deltaMode || canDeltaBackup(get(() => hosts[pool.master].version)), (_, { deltaMode, hosts }) =>
pool =>
!deltaMode || canDeltaBackup(get(() => hosts[pool.master].version)),
pools: (_, { pattern }) => (pattern.$pool !== undefined ? destructSmartPattern(pattern.$pool) : {}), pools: (_, { pattern }) => (pattern.$pool !== undefined ? destructSmartPattern(pattern.$pool) : {}),
}, },
}), }),

View File

@ -238,21 +238,23 @@ const AttachedVdisTable = decorate([
vdis: createGetObjectsOfType('VDI'), vdis: createGetObjectsOfType('VDI'),
vdiSnapshots: createGetObjectsOfType('VDI-snapshot'), vdiSnapshots: createGetObjectsOfType('VDI-snapshot'),
}), }),
({ columns, rowTransform }) => ({ pools, srs, vbds, vdis, vdiSnapshots }) => ( ({ columns, rowTransform }) =>
<NoObjects ({ pools, srs, vbds, vdis, vdiSnapshots }) =>
actions={CONTROL_DOMAIN_VDIS_ACTIONS} (
collection={vbds} <NoObjects
columns={columns} actions={CONTROL_DOMAIN_VDIS_ACTIONS}
component={SortedTable} collection={vbds}
data-pools={pools} columns={columns}
data-srs={srs} component={SortedTable}
data-vdis={vdis} data-pools={pools}
data-vdiSnapshots={vdiSnapshots} data-srs={srs}
emptyMessage={_('noControlDomainVdis')} data-vdis={vdis}
rowTransform={rowTransform} data-vdiSnapshots={vdiSnapshots}
stateUrlParam='s_controldomain' emptyMessage={_('noControlDomainVdis')}
/> rowTransform={rowTransform}
), stateUrlParam='s_controldomain'
/>
),
{ {
columns: [ columns: [
{ {

View File

@ -71,25 +71,31 @@ const DiskImport = decorate([
) )
return { disks: disks.filter(disk => disk !== undefined), loadingDisks: false } return { disks: disks.filter(disk => disk !== undefined), loadingDisks: false }
}, },
import: () => async ({ disks, mapDescriptions, mapNames, sr }) => { import:
await importDisks( () =>
disks.map(({ id, name, ...disk }) => ({ async ({ disks, mapDescriptions, mapNames, sr }) => {
...disk, await importDisks(
name: mapNames[id] || name, disks.map(({ id, name, ...disk }) => ({
description: mapDescriptions[id], ...disk,
})), name: mapNames[id] || name,
sr description: mapDescriptions[id],
) })),
}, sr
)
},
linkState, linkState,
onChangeDescription: (_, { target: { name, value } }) => ({ mapDescriptions }) => { onChangeDescription:
mapDescriptions[name] = value (_, { target: { name, value } }) =>
return { mapDescriptions } ({ mapDescriptions }) => {
}, mapDescriptions[name] = value
onChangeName: (_, { target: { name, value } }) => ({ mapNames }) => { return { mapDescriptions }
mapNames[name] = value },
return { mapNames } onChangeName:
}, (_, { target: { name, value } }) =>
({ mapNames }) => {
mapNames[name] = value
return { mapNames }
},
onChangeSr: (_, sr) => ({ sr }), onChangeSr: (_, sr) => ({ sr }),
reset: getInitialState, reset: getInitialState,
}, },

View File

@ -928,14 +928,8 @@ export default class Home extends Component {
} = this.state } = this.state
const options = OPTIONS[type] const options = OPTIONS[type]
const { const { filters, mainActions, otherActions, showHostsSelector, showPoolsSelector, showResourceSetsSelector } =
filters, options
mainActions,
otherActions,
showHostsSelector,
showPoolsSelector,
showResourceSetsSelector,
} = options
return ( return (
<Container> <Container>

View File

@ -53,19 +53,33 @@ const isRunning = host => host && host.power_state === 'Running'
const getPool = createGetObject((state, props) => getHost(state, props).$pool) const getPool = createGetObject((state, props) => getHost(state, props).$pool)
const getVmController = createGetObjectsOfType('VM-controller').find( const getVmController = createGetObjectsOfType('VM-controller').find(
createSelector(getHost, ({ id }) => obj => obj.$container === id) createSelector(
getHost,
({ id }) =>
obj =>
obj.$container === id
)
) )
const getHostVms = createGetObjectsOfType('VM').filter( const getHostVms = createGetObjectsOfType('VM').filter(
createSelector(getHost, ({ id }) => obj => obj.$container === id) createSelector(
getHost,
({ id }) =>
obj =>
obj.$container === id
)
) )
const getNumberOfVms = getHostVms.count() const getNumberOfVms = getHostVms.count()
const getLogs = createGetObjectsOfType('message') const getLogs = createGetObjectsOfType('message')
.filter( .filter(
createSelector(getHost, getVmController, (host, controller) => ({ $object }) => createSelector(
$object === host.id || $object === (controller !== undefined && controller.id) getHost,
getVmController,
(host, controller) =>
({ $object }) =>
$object === host.id || $object === (controller !== undefined && controller.id)
) )
) )
.sort() .sort()

View File

@ -88,8 +88,10 @@ const SetControlDomainMemory = ({ value, onChange }) => (
const MultipathableSrs = decorate([ const MultipathableSrs = decorate([
connectStore({ connectStore({
pbds: createGetObjectsOfType('PBD').filter((_, { hostId }) => pbd => pbds: createGetObjectsOfType('PBD').filter(
pbd.host === hostId && Boolean(pbd.otherConfig.multipathed) (_, { hostId }) =>
pbd =>
pbd.host === hostId && Boolean(pbd.otherConfig.multipathed)
), ),
}), }),
({ pbds }) => ({ pbds }) =>

View File

@ -44,8 +44,14 @@ export default decorate([
}, },
}, },
computed: { computed: {
networkPredicate: (_, { value: { pool } }) => network => pool.id === network.$pool, networkPredicate:
srPredicate: (_, { value }) => sr => sr.$pool === get(() => value.pool.id) && isSrWritable(sr), (_, { value: { pool } }) =>
network =>
pool.id === network.$pool,
srPredicate:
(_, { value }) =>
sr =>
sr.$pool === get(() => value.pool.id) && isSrWritable(sr),
}, },
}), }),
injectState, injectState,

View File

@ -47,15 +47,8 @@ export default decorate([
}), }),
effects: { effects: {
async install() { async install() {
const { const { id, name, namespace, markHubResourceAsInstalled, markHubResourceAsInstalling, templates, version } =
id, this.props
name,
namespace,
markHubResourceAsInstalled,
markHubResourceAsInstalling,
templates,
version,
} = this.props
const { isTemplateInstalled } = this.state const { isTemplateInstalled } = this.state
const resourceParams = await form({ const resourceParams = await form({
defaultValue: { defaultValue: {
@ -227,10 +220,14 @@ export default decorate([
isTemplateInstalledOnAllPools: ({ installedTemplates }, { pools }) => isTemplateInstalledOnAllPools: ({ installedTemplates }, { pools }) =>
installedTemplates.length > 0 && installedTemplates.length > 0 &&
pools.every(pool => installedTemplates.find(template => template.$pool === pool.id) !== undefined), pools.every(pool => installedTemplates.find(template => template.$pool === pool.id) !== undefined),
isTemplateInstalled: ({ installedTemplates }) => pool => isTemplateInstalled:
installedTemplates.find(template => template.$pool === pool.id) === undefined, ({ installedTemplates }) =>
isPoolCreated: ({ installedTemplates }) => pool => pool =>
installedTemplates.find(template => template.$pool === pool.id) !== undefined, installedTemplates.find(template => template.$pool === pool.id) === undefined,
isPoolCreated:
({ installedTemplates }) =>
pool =>
installedTemplates.find(template => template.$pool === pool.id) !== undefined,
}, },
}), }),
injectState, injectState,

View File

@ -307,14 +307,16 @@ export default decorate([
this.state._status = status this.state._status = status
this.state.page = 1 this.state.page = 1
}, },
restartVmJob: (_, params) => async (_, { log: { scheduleId, jobId } }) => { restartVmJob:
await runBackupNgJob({ (_, params) =>
force: get(() => params.force), async (_, { log: { scheduleId, jobId } }) => {
id: jobId, await runBackupNgJob({
schedule: scheduleId, force: get(() => params.force),
vm: get(() => params.vm), id: jobId,
}) schedule: scheduleId,
}, vm: get(() => params.vm),
})
},
}, },
computed: { computed: {
log: (_, { log, pools, vms }) => { log: (_, { log, pools, vms }) => {
@ -361,11 +363,14 @@ export default decorate([
const start = (page - 1) * ITEMS_PER_PAGE const start = (page - 1) * ITEMS_PER_PAGE
return tasksFilteredByStatus.slice(start, start + ITEMS_PER_PAGE) return tasksFilteredByStatus.slice(start, start + ITEMS_PER_PAGE)
}, },
optionRenderer: ({ countByStatus }) => ({ label, value }) => ( optionRenderer:
<span> ({ countByStatus }) =>
{_(label)} ({countByStatus[value] || 0}) ({ label, value }) =>
</span> (
), <span>
{_(label)} ({countByStatus[value] || 0})
</span>
),
countByStatus: ({ preFilteredTasksLogs }) => ({ countByStatus: ({ preFilteredTasksLogs }) => ({
all: get(() => preFilteredTasksLogs.length), all: get(() => preFilteredTasksLogs.length),
...countBy(preFilteredTasksLogs, 'status'), ...countBy(preFilteredTasksLogs, 'status'),

View File

@ -27,33 +27,37 @@ export default decorate([
})), })),
provideState({ provideState({
effects: { effects: {
_downloadLog: () => ({ formattedLog }, { log }) => _downloadLog:
downloadLog({ log: formattedLog, date: log.start, type: 'backup NG' }), () =>
restartFailedVms: (_, params) => async (_, { log: { jobId: id, scheduleId: schedule, tasks, infos } }) => { ({ formattedLog }, { log }) =>
let vms downloadLog({ log: formattedLog, date: log.start, type: 'backup NG' }),
if (tasks !== undefined) { restartFailedVms:
const scheduledVms = get(() => infos.find(({ message }) => message === 'vms').data.vms) (_, params) =>
async (_, { log: { jobId: id, scheduleId: schedule, tasks, infos } }) => {
let vms
if (tasks !== undefined) {
const scheduledVms = get(() => infos.find(({ message }) => message === 'vms').data.vms)
if (scheduledVms !== undefined) { if (scheduledVms !== undefined) {
vms = new Set(scheduledVms) vms = new Set(scheduledVms)
tasks.forEach(({ status, data: { id } }) => { tasks.forEach(({ status, data: { id } }) => {
status === 'success' && vms.delete(id) status === 'success' && vms.delete(id)
}) })
vms = Array.from(vms) vms = Array.from(vms)
} else { } else {
vms = [] vms = []
tasks.forEach(({ status, data: { id } }) => { tasks.forEach(({ status, data: { id } }) => {
status !== 'success' && vms.push(id) status !== 'success' && vms.push(id)
}) })
}
} }
} await runBackupNgJob({
await runBackupNgJob({ force: get(() => params.force),
force: get(() => params.force), id,
id, schedule,
schedule, vms,
vms, })
}) },
},
}, },
computed: { computed: {
formattedLog: (_, { log }) => JSON.stringify(log, null, 2), formattedLog: (_, { log }) => JSON.stringify(log, null, 2),

View File

@ -260,7 +260,7 @@ export default class NewVm extends BaseComponent {
() => this.props.resourceSets, () => this.props.resourceSets,
createSelector( createSelector(
() => this.props.location.query.resourceSet, () => this.props.location.query.resourceSet,
resourceSetId => resourceSet => (resourceSet !== undefined ? resourceSetId === resourceSet.id : undefined) resourceSetId => resourceSet => resourceSet !== undefined ? resourceSetId === resourceSet.id : undefined
) )
) )
@ -582,7 +582,9 @@ export default class NewVm extends BaseComponent {
const { pool } = this.props const { pool } = this.props
return pool && pool.id return pool && pool.id
}, },
poolId => ({ $pool }) => $pool === poolId poolId =>
({ $pool }) =>
$pool === poolId
) )
_getIsInResourceSet = createSelector( _getIsInResourceSet = createSelector(
() => { () => {
@ -592,8 +594,10 @@ export default class NewVm extends BaseComponent {
objectsIds => id => includes(objectsIds, id) objectsIds => id => includes(objectsIds, id)
) )
_getVmPredicate = createSelector(this._getIsInPool, this._getIsInResourceSet, (isInPool, isInResourceSet) => vm => _getVmPredicate = createSelector(
isInResourceSet(vm.id) || isInPool(vm) this._getIsInPool,
this._getIsInResourceSet,
(isInPool, isInResourceSet) => vm => isInResourceSet(vm.id) || isInPool(vm)
) )
_getSrPredicate = createSelector( _getSrPredicate = createSelector(
this._getIsInPool, this._getIsInPool,

View File

@ -102,7 +102,10 @@ const NewNetwork = decorate([
initialize: async () => ({ bondModes: await getBondModes() }), initialize: async () => ({ bondModes: await getBondModes() }),
linkState, linkState,
onChangeMode: (_, bondMode) => ({ bondMode }), onChangeMode: (_, bondMode) => ({ bondMode }),
onChangePif: (_, value) => ({ bonded }) => (bonded ? { pifs: value } : { pif: value }), onChangePif:
(_, value) =>
({ bonded }) =>
bonded ? { pifs: value } : { pif: value },
onChangeEncapsulation(_, encapsulation) { onChangeEncapsulation(_, encapsulation) {
return { encapsulation: encapsulation.value } return { encapsulation: encapsulation.value }
}, },
@ -144,16 +147,29 @@ const NewNetwork = decorate([
value: mode, value: mode,
})) }))
: [], : [],
hostPredicate: ({ networks }, { pool }) => host => hostPredicate:
host.$pool === pool.id || networks.some(({ pool }) => pool !== undefined && pool.id === host.$pool), ({ networks }, { pool }) =>
pifPredicate: (_, { pool }) => pif => !pif.isBondSlave && pif.vlan === -1 && pif.$host === (pool && pool.master), host =>
pifPredicateSdnController: (_, { pool }) => pif => canSupportPrivateNetwork(pool, pif), host.$pool === pool.id || networks.some(({ pool }) => pool !== undefined && pool.id === host.$pool),
networkPifPredicate: ({ networks }) => (pif, key) => canSupportPrivateNetwork(networks[key].pool, pif), pifPredicate:
networkPoolPredicate: ({ networks }, { pool: rootPool }) => (pool, index) => (_, { pool }) =>
pool.id !== rootPool.id && pif =>
!networks.some( !pif.isBondSlave && pif.vlan === -1 && pif.$host === (pool && pool.master),
({ pool: networksPool = {} }, networksIndex) => pool.id === networksPool.id && index !== networksIndex pifPredicateSdnController:
), (_, { pool }) =>
pif =>
canSupportPrivateNetwork(pool, pif),
networkPifPredicate:
({ networks }) =>
(pif, key) =>
canSupportPrivateNetwork(networks[key].pool, pif),
networkPoolPredicate:
({ networks }, { pool: rootPool }) =>
(pool, index) =>
pool.id !== rootPool.id &&
!networks.some(
({ pool: networksPool = {} }, networksIndex) => pool.id === networksPool.id && index !== networksIndex
),
isSdnControllerLoaded: (state, { plugins = [] }) => isSdnControllerLoaded: (state, { plugins = [] }) =>
plugins.some(plugin => plugin.name === 'sdn-controller' && plugin.loaded), plugins.some(plugin => plugin.name === 'sdn-controller' && plugin.loaded),
}, },

View File

@ -35,23 +35,56 @@ import TabPatches from './tab-patches'
const getMaster = createGetObject((state, props) => getPool(state, props).master) const getMaster = createGetObject((state, props) => getPool(state, props).master)
const getNetworks = createGetObjectsOfType('network') const getNetworks = createGetObjectsOfType('network')
.filter(createSelector(getPool, ({ id }) => network => network.$pool === id)) .filter(
createSelector(
getPool,
({ id }) =>
network =>
network.$pool === id
)
)
.sort() .sort()
const getPifs = createGetObjectsOfType('PIF') const getPifs = createGetObjectsOfType('PIF')
.filter(createSelector(getPool, ({ id }) => pif => pif.$pool === id)) .filter(
createSelector(
getPool,
({ id }) =>
pif =>
pif.$pool === id
)
)
.sort() .sort()
const getHosts = createGetObjectsOfType('host') const getHosts = createGetObjectsOfType('host')
.filter(createSelector(getPool, ({ id }) => obj => obj.$pool === id)) .filter(
createSelector(
getPool,
({ id }) =>
obj =>
obj.$pool === id
)
)
.sort() .sort()
const getPoolSrs = createGetObjectsOfType('SR') const getPoolSrs = createGetObjectsOfType('SR')
.filter(createSelector(getPool, ({ id }) => sr => sr.$pool === id)) .filter(
createSelector(
getPool,
({ id }) =>
sr =>
sr.$pool === id
)
)
.sort() .sort()
const getNumberOfVms = createGetObjectsOfType('VM').count( const getNumberOfVms = createGetObjectsOfType('VM').count(
createSelector(getPool, ({ id }) => obj => obj.$pool === id) createSelector(
getPool,
({ id }) =>
obj =>
obj.$pool === id
)
) )
const getLogs = createGetObjectMessages(getPool) const getLogs = createGetObjectMessages(getPool)

View File

@ -61,7 +61,12 @@ class PoolMaster extends Component {
.sort() .sort()
return { return {
hosts: getHosts, hosts: getHosts,
hostsByMultipathing: createGroupBy(getHosts, () => ({ multipathing }) => (multipathing ? 'enabled' : 'disabled')), hostsByMultipathing: createGroupBy(
getHosts,
() =>
({ multipathing }) =>
multipathing ? 'enabled' : 'disabled'
),
gpuGroups: createGetObjectsOfType('gpuGroup') gpuGroups: createGetObjectsOfType('gpuGroup')
.filter((_, { pool }) => ({ $pool: pool.id })) .filter((_, { pool }) => ({ $pool: pool.id }))
.sort(), .sort(),

View File

@ -79,8 +79,10 @@ const Modal = decorate([
idSelectSr: generateId, idSelectSr: generateId,
isStaticMode: (state, { value }) => value.networkMode === 'static', isStaticMode: (state, { value }) => value.networkMode === 'static',
srPredicate: (state, { pbds, hosts }) => sr => srPredicate:
isSrWritable(sr) && sr.$PBDs.some(pbd => get(() => hosts[pbds[pbd].host].hvmCapable)), (state, { pbds, hosts }) =>
sr =>
isSrWritable(sr) && sr.$PBDs.some(pbd => get(() => hosts[pbds[pbd].host].hvmCapable)),
networkPredicate: (state, { value }) => value.sr && (network => value.sr.$pool === network.$pool), networkPredicate: (state, { value }) => value.sr && (network => value.sr.$pool === network.$pool),
}, },
}), }),

View File

@ -145,7 +145,9 @@ export default class Acls extends Component {
_getObjectPredicate = createSelector( _getObjectPredicate = createSelector(
() => this.state.typeFilters, () => this.state.typeFilters,
() => this.state.someTypeFilters, () => this.state.someTypeFilters,
(typeFilters, someTypeFilters) => ({ type }) => !someTypeFilters || typeFilters[type] (typeFilters, someTypeFilters) =>
({ type }) =>
!someTypeFilters || typeFilters[type]
) )
_selectAll = () => { _selectAll = () => {

View File

@ -62,31 +62,39 @@ export default decorate([
provideState({ provideState({
initialState: () => initialParams, initialState: () => initialParams,
effects: { effects: {
setInputValue: (_, { target: { name, value } }) => state => ({ setInputValue:
...state, (_, { target: { name, value } }) =>
[name]: value, state => ({
}), ...state,
[name]: value,
}),
reset: () => state => ({ reset: () => state => ({
...state, ...state,
...initialParams, ...initialParams,
}), }),
createCloudConfig: ({ reset }) => async ({ name, template = DEFAULT_CLOUD_CONFIG_TEMPLATE }) => { createCloudConfig:
await createCloudConfig({ name, template }) ({ reset }) =>
reset() async ({ name, template = DEFAULT_CLOUD_CONFIG_TEMPLATE }) => {
}, await createCloudConfig({ name, template })
editCloudConfig: ({ reset }) => async ({ name, template, cloudConfigToEditId }, { cloudConfigs }) => { reset()
const oldCloudConfig = find(cloudConfigs, { id: cloudConfigToEditId }) },
if (oldCloudConfig.name !== name || oldCloudConfig.template !== template) { editCloudConfig:
await editCloudConfig(cloudConfigToEditId, { name, template }) ({ reset }) =>
} async ({ name, template, cloudConfigToEditId }, { cloudConfigs }) => {
reset() const oldCloudConfig = find(cloudConfigs, { id: cloudConfigToEditId })
}, if (oldCloudConfig.name !== name || oldCloudConfig.template !== template) {
populateForm: (_, { id, name, template }) => state => ({ await editCloudConfig(cloudConfigToEditId, { name, template })
...state, }
name, reset()
cloudConfigToEditId: id, },
template, populateForm:
}), (_, { id, name, template }) =>
state => ({
...state,
name,
cloudConfigToEditId: id,
template,
}),
}, },
computed: { computed: {
formId: generateId, formId: generateId,

View File

@ -51,92 +51,96 @@ export default decorate([
setProxy(_, proxy) { setProxy(_, proxy) {
this.state.proxyId = resolveId(proxy) this.state.proxyId = resolveId(proxy)
}, },
editRemote: ({ reset }) => state => { editRemote:
const { ({ reset }) =>
remote, state => {
domain = remote.domain || '', const {
host = remote.host, remote,
name, domain = remote.domain || '',
options = remote.options || '', host = remote.host,
password = remote.password, name,
port = remote.port, options = remote.options || '',
proxyId = remote.proxy, password = remote.password,
type = remote.type, port = remote.port,
username = remote.username, proxyId = remote.proxy,
protocol = remote.protocol || 'https', type = remote.type,
region = remote.region, username = remote.username,
} = state protocol = remote.protocol || 'https',
let { path = remote.path } = state region = remote.region,
if (type === 's3') { } = state
const { parsedPath, bucket = parsedPath.split('/')[0], directory = parsedPath.split('/')[1] } = state let { path = remote.path } = state
path = bucket + '/' + directory if (type === 's3') {
} const { parsedPath, bucket = parsedPath.split('/')[0], directory = parsedPath.split('/')[1] } = state
return editRemote(remote, { path = bucket + '/' + directory
name, }
url: format({ return editRemote(remote, {
domain, name,
url: format({
domain,
host,
password,
path,
port: port || undefined,
type,
username,
protocol,
region,
}),
options: options !== '' ? options : null,
proxy: proxyId,
}).then(reset)
},
createRemote:
({ reset }) =>
async (state, { remotes }) => {
if (some(remotes, { name: state.name })) {
return alert(
<span>
<Icon icon='error' /> {_('remoteTestName')}
</span>,
<p>{_('remoteTestNameFailure')}</p>
)
}
const {
domain = 'WORKGROUP',
host, host,
name,
options,
password, password,
path, path,
port: port || undefined, port,
type, proxyId,
type = 'nfs',
username, username,
protocol, } = state
region,
}),
options: options !== '' ? options : null,
proxy: proxyId,
}).then(reset)
},
createRemote: ({ reset }) => async (state, { remotes }) => {
if (some(remotes, { name: state.name })) {
return alert(
<span>
<Icon icon='error' /> {_('remoteTestName')}
</span>,
<p>{_('remoteTestNameFailure')}</p>
)
}
const { const urlParams = {
domain = 'WORKGROUP', host,
host, path,
name, port,
options, type,
password, }
path, if (type === 's3') {
port, const { bucket, directory } = state
proxyId, urlParams.path = bucket + '/' + directory
type = 'nfs', }
username, username && (urlParams.username = username)
} = state password && (urlParams.password = password)
domain && (urlParams.domain = domain)
const urlParams = { if (type === 'file') {
host, await confirm({
path, title: _('localRemoteWarningTitle'),
port, body: _('localRemoteWarningMessage'),
type, })
} }
if (type === 's3') {
const { bucket, directory } = state
urlParams.path = bucket + '/' + directory
}
username && (urlParams.username = username)
password && (urlParams.password = password)
domain && (urlParams.domain = domain)
if (type === 'file') { const url = format(urlParams)
await confirm({ return createRemote(name, url, options !== '' ? options : undefined, proxyId === null ? undefined : proxyId)
title: _('localRemoteWarningTitle'), .then(reset)
body: _('localRemoteWarningMessage'), .catch(err => error('Create Remote', err.message || String(err)))
}) },
}
const url = format(urlParams)
return createRemote(name, url, options !== '' ? options : undefined, proxyId === null ? undefined : proxyId)
.then(reset)
.catch(err => error('Create Remote', err.message || String(err)))
},
setSecretKey(_, { target: { value } }) { setSecretKey(_, { target: { value } }) {
this.state.password = value this.state.password = value
}, },

View File

@ -210,16 +210,18 @@ export default class TabGeneral extends Component {
} }
) )
_getGenerateLink = createSelector(this._getDiskGroups, diskGroups => ids => _getGenerateLink = createSelector(
`#/srs/${this.props.sr.id}/disks?s=${encodeURIComponent( this._getDiskGroups,
`id:|(${flattenDeep( diskGroups => ids =>
map(pick(keyBy(diskGroups, 'id'), ids), ({ id, baseCopies, vdis, snapshots, type }) => `#/srs/${this.props.sr.id}/disks?s=${encodeURIComponent(
type === 'orphanedSnapshot' ? id : [map(baseCopies, 'id'), map(vdis, 'id'), map(snapshots, 'id')] `id:|(${flattenDeep(
map(pick(keyBy(diskGroups, 'id'), ids), ({ id, baseCopies, vdis, snapshots, type }) =>
type === 'orphanedSnapshot' ? id : [map(baseCopies, 'id'), map(vdis, 'id'), map(snapshots, 'id')]
)
) )
) .sort()
.sort() .join(' ')})`
.join(' ')})` )}`
)}`
) )
render() { render() {

View File

@ -79,7 +79,11 @@ const Field = ({ title, children }) => (
}) })
class Node extends Component { class Node extends Component {
_replaceBrick = async ({ brick, vm }) => { _replaceBrick = async ({ brick, vm }) => {
const { sr, brickSize, onSameVm = false } = await confirm({ const {
sr,
brickSize,
onSameVm = false,
} = await confirm({
icon: 'refresh', icon: 'refresh',
title: _('xosanReplace'), title: _('xosanReplace'),
body: <ReplaceBrickModalBody vm={vm} />, body: <ReplaceBrickModalBody vm={vm} />,

View File

@ -87,8 +87,9 @@ const shareVmProxy = vm => shareVm(vm, vm.resourceSet)
const getSrs = createGetObjectsOfType('SR').pick(createSelector(getVdis, vdis => uniq(map(vdis, '$SR')))) const getSrs = createGetObjectsOfType('SR').pick(createSelector(getVdis, vdis => uniq(map(vdis, '$SR'))))
const getSrsContainers = createSelector(getSrs, srs => uniq(map(srs, '$container'))) const getSrsContainers = createSelector(getSrs, srs => uniq(map(srs, '$container')))
const getAffinityHostPredicate = createSelector(getSrsContainers, containers => host => const getAffinityHostPredicate = createSelector(
every(containers, container => container === host.$pool || container === host.id) getSrsContainers,
containers => host => every(containers, container => container === host.$pool || container === host.id)
) )
return { return {
@ -275,7 +276,9 @@ class AddAclsModal extends Component {
_getPredicate = createSelector( _getPredicate = createSelector(
() => this.props.acls, () => this.props.acls,
() => this.props.vm, () => this.props.vm,
(acls, object) => ({ id: subject, permission }) => permission !== 'admin' && !some(acls, { object, subject }) (acls, object) =>
({ id: subject, permission }) =>
permission !== 'admin' && !some(acls, { object, subject })
) )
render() { render() {
@ -306,25 +309,29 @@ const Acls = decorate([
}), }),
provideState({ provideState({
effects: { effects: {
addAcls: () => (state, { acls, vm }) => addAcls:
confirm({ () =>
title: _('vmAddAcls'), (state, { acls, vm }) =>
body: <AddAclsModal acls={acls} vm={vm} />, confirm({
}) title: _('vmAddAcls'),
.then(({ action, subjects }) => { body: <AddAclsModal acls={acls} vm={vm} />,
if (action == null || isEmpty(subjects)) {
return error(_('addAclsErrorTitle'), _('addAclsErrorMessage'))
}
return Promise.all(map(subjects, subject => addAcl({ subject, object: vm, action })))
}) })
.catch(err => err && error(_('addAclsErrorTitle'), err.message || String(err))), .then(({ action, subjects }) => {
removeAcl: (_, { currentTarget: { dataset } }) => (_, { vm: object }) => if (action == null || isEmpty(subjects)) {
removeAcl({ return error(_('addAclsErrorTitle'), _('addAclsErrorMessage'))
action: dataset.action, }
object,
subject: dataset.subject, return Promise.all(map(subjects, subject => addAcl({ subject, object: vm, action })))
}), })
.catch(err => err && error(_('addAclsErrorTitle'), err.message || String(err))),
removeAcl:
(_, { currentTarget: { dataset } }) =>
(_, { vm: object }) =>
removeAcl({
action: dataset.action,
object,
subject: dataset.subject,
}),
}, },
computed: { computed: {
rawAcls: (_, { acls, vm }) => filter(acls, { object: vm }), rawAcls: (_, { acls, vm }) => filter(acls, { object: vm }),

View File

@ -18,7 +18,10 @@ const BackupTab = decorate([
provideState({ provideState({
computed: { computed: {
jobIds: ({ predicate }, { jobs }) => filter(jobs, predicate).map(_ => _.id), jobIds: ({ predicate }, { jobs }) => filter(jobs, predicate).map(_ => _.id),
predicate: (_, { vm }) => ({ vms }) => vms !== undefined && createPredicate(omit(vms, 'power_state'))(vm), predicate:
(_, { vm }) =>
({ vms }) =>
vms !== undefined && createPredicate(omit(vms, 'power_state'))(vm),
}, },
}), }),
injectState, injectState,

View File

@ -565,8 +565,10 @@ export default class TabDisks extends Component {
} }
) )
_getCheckSr = createSelector(this._getRequiredHost, requiredHost => sr => _getCheckSr = createSelector(
sr === undefined || isSrShared(sr) || requiredHost === undefined || sr.$container === requiredHost this._getRequiredHost,
requiredHost => sr =>
sr === undefined || isSrShared(sr) || requiredHost === undefined || sr.$container === requiredHost
) )
_getVbds = createSelector( _getVbds = createSelector(
@ -597,12 +599,14 @@ export default class TabDisks extends Component {
) )
) )
_getGenerateWarningBeforeMigrate = createSelector(this._getCheckSr, check => sr => _getGenerateWarningBeforeMigrate = createSelector(
check(sr) ? null : ( this._getCheckSr,
<span className='text-warning'> check => sr =>
<Icon icon='alarm' /> {_('warningVdiSr')} check(sr) ? null : (
</span> <span className='text-warning'>
) <Icon icon='alarm' /> {_('warningVdiSr')}
</span>
)
) )
actions = [ actions = [

View File

@ -129,9 +129,10 @@ export default class Xosan extends Component {
} }
) )
_getAvailableLicenses = createFilter(() => this.props.xosanLicenses, [ _getAvailableLicenses = createFilter(
({ boundObjectId, expires }) => boundObjectId === undefined && (expires === undefined || expires > Date.now()), () => this.props.xosanLicenses,
]) [({ boundObjectId, expires }) => boundObjectId === undefined && (expires === undefined || expires > Date.now())]
)
_getKnownXosans = createSelector( _getKnownXosans = createSelector(
createSelector( createSelector(

View File

@ -237,8 +237,10 @@ const XOSAN_INDIVIDUAL_ACTIONS = [
return hostsNeedRestartByPool return hostsNeedRestartByPool
}) })
const getPoolPredicate = createSelector(getXosanSrs, getHosts, (srs, hosts) => pool => const getPoolPredicate = createSelector(
hosts[pool.master].productBrand !== 'XCP-ng' && every(srs, sr => sr.$pool !== pool.id) getXosanSrs,
getHosts,
(srs, hosts) => pool => hosts[pool.master].productBrand !== 'XCP-ng' && every(srs, sr => sr.$pool !== pool.id)
) )
return { return {