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 {
constructor(storage: Storage) { }
public add(subject: any, event: string, data: any): Promise<Record> { }
public checkIntegrity(oldest: string, newest: string): Promise<number> { }
public getFrom(newest?: string): AsyncIterator { }
public deleteFrom(newest: string): Promise<void> { }
public deleteRangeAndRewrite(newest: string, oldest: string): Promise<void> { }
constructor(storage: Storage) {}
public add(subject: any, event: string, data: any): Promise<Record> {}
public checkIntegrity(oldest: string, newest: string): Promise<number> {}
public getFrom(newest?: string): AsyncIterator {}
public deleteFrom(newest: 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({
keyTransform: key => key.slice(5).toLowerCase(),
})
const makeFunction = command => async (fields, ...args) => {
const info = await fromCallback(execFile, command, [
'--noheading',
'--nosuffix',
'--nameprefixes',
'--unbuffered',
'--units',
'b',
'-o',
String(fields),
...args,
])
return info
.trim()
.split(/\r?\n/)
.map(Array.isArray(fields) ? parse : line => parse(line)[fields])
}
const makeFunction =
command =>
async (fields, ...args) => {
const info = await fromCallback(execFile, command, [
'--noheading',
'--nosuffix',
'--nameprefixes',
'--unbuffered',
'--units',
'b',
'-o',
String(fields),
...args,
])
return info
.trim()
.split(/\r?\n/)
.map(Array.isArray(fields) ? parse : line => parse(line)[fields])
}
exports.lvs = makeFunction('lvs')
exports.pvs = makeFunction('pvs')

View File

@ -20,36 +20,8 @@ if (process.stdout !== undefined && process.stdout.isTTY && process.stderr !== u
}
const NAMESPACE_COLORS = [
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,
200,
199,
198,
197,
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,
200, 199, 198, 197,
]
formatNamespace = namespace => {
// 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),
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(
string
)
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(
string
)
assert(eth !== null)
buffer.writeUInt8(parseInt(eth[1], 16), offset)
buffer.writeUInt8(parseInt(eth[2], 16), offset + 1)
@ -50,9 +51,10 @@ export default {
),
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(
string
)
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(
string
)
assert(ip !== null)
buffer.writeUInt8(parseInt(ip[1], 10), offset)
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 { _: args, file, help, host, raw, token } = getopts(argv, {
const {
_: args,
file,
help,
host,
raw,
token,
} = getopts(argv, {
alias: { file: 'f', help: 'h' },
boolean: ['help', 'raw'],
default: {

View File

@ -93,10 +93,7 @@ declare namespace event {
snapshot: Task
}
function from(_: {
token: string = ''
timeout?: number
}): {
function from(_: { token: string = ''; timeout?: number }): {
events: Event[]
token: string
}
@ -146,13 +143,13 @@ declare namespace backup {
streamLogs: boolean = false
}): string
function listPoolMetadataBackups(_: {
remotes: { [id: string]: Remote }
}): { [remoteId: string]: { [poolUuid: string]: object[] } }
function listPoolMetadataBackups(_: { remotes: { [id: string]: Remote } }): {
[remoteId: string]: { [poolUuid: string]: object[] }
}
function listVmBackups(_: {
remotes: { [remoteId: string]: Remote }
}): { [remoteId: string]: { [vmUuid: string]: object[] } }
function listVmBackups(_: { remotes: { [remoteId: string]: Remote } }): {
[remoteId: string]: { [vmUuid: 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
XCP-ng doesn't limit VMs to 32 vCPU
:::
### 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.
@ -491,10 +492,12 @@ If you are behind a proxy, please update your `xo-server` configuration to add a
::: 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.
:::
::: 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.
- All the hosts in a pool must run the same XCP-ng version.
:::
### 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).

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.
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>
```
Where UUID is the uuid of your 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).
Then set the new IP:
```
$ xoa network static eth1
? Static IP for this machine 192.168.100.120
? Network mask (eg 255.255.255.0) 255.255.255.0
```
If you want to set a static address.
```
$ xoa network dhcp eth1
```
If you prefer using DHCP.
:::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.

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
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>
```
Where UUID is the uuid of your XOA 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
// eslint-disable-next-line no-unused-vars
const and = (...checkers) => (object, permission) => {
for (const checker of checkers) {
if (!checker(object, permission)) {
return false
const and =
(...checkers) =>
(object, permission) => {
for (const checker of checkers) {
if (!checker(object, permission)) {
return false
}
}
return true
}
return true
}
// eslint-disable-next-line no-unused-vars
const or = (...checkers) => (object, permission) => {
for (const checker of checkers) {
if (checker(object, permission)) {
return true
const or =
(...checkers) =>
(object, permission) => {
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 Xo from 'xo-lib'
const parseBoolean = (
value: string,
defaultValue?: boolean
): boolean | undefined => {
const parseBoolean = (value: string, defaultValue?: boolean): boolean | undefined => {
if (value === undefined || value === '') {
return defaultValue
}
@ -49,30 +46,24 @@ execPromise(
const errors: any[] = []
const stream = process.stdin.pipe(csvParser()).pipe(
through2.obj(
(
{ allowUnauthorized, autoConnect, host, label, password, username },
_,
next
) => {
console.log('server', host)
through2.obj(({ allowUnauthorized, autoConnect, host, label, password, username }, _, next) => {
console.log('server', host)
xo.call('server.add', {
allowUnauthorized: parseBoolean(allowUnauthorized),
autoConnect: parseBoolean(autoConnect, false),
host,
label,
password,
username,
}).then(
() => next(),
(error: any) => {
errors.push({ host, error })
return next()
}
)
}
)
xo.call('server.add', {
allowUnauthorized: parseBoolean(allowUnauthorized),
autoConnect: parseBoolean(autoConnect, false),
host,
label,
password,
username,
}).then(
() => next(),
(error: any) => {
errors.push({ host, error })
return next()
}
)
})
)
await new Promise((resolve, reject) => {

View File

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

View File

@ -8,7 +8,7 @@ export default function globMatcher(patterns, opts) {
if (!Array.isArray(patterns)) {
if (patterns[0] === '!') {
const m = matcher(patterns.slice(1), opts)
return function(string) {
return function (string) {
return !m(string)
}
} else {
@ -32,7 +32,7 @@ export default function globMatcher(patterns, opts) {
const nNone = noneMustMatch.length
const nAny = anyMustMatch.length
return function(string) {
return function (string) {
if (typeof string !== 'string') {
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 } ] ] )
// => [ { a: 2, c: 5 }, { b: 3, c: 5 }, { a: 2, d: 7 }, { b: 3, d: 7 } ]
export const crossProduct = (vectors, mergeFn = mergeObjects) => cb =>
combine(vectors)(vector => {
cb(mergeFn(vector))
})
export const crossProduct =
(vectors, mergeFn = mergeObjects) =>
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)
// Patch getObject to resolve _xapiId property.
this.getObject = (getObject => (...args) => {
let tmp
if ((tmp = args[0]) != null && (tmp = tmp._xapiId) != null) {
args[0] = tmp
this.getObject = (
getObject =>
(...args) => {
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.

View File

@ -68,7 +68,9 @@ export default class IpPools {
if (await store.has(id)) {
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)
return this._app.removeIpPoolFromResourceSet(id, set.id)
})

View File

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

View File

@ -131,50 +131,56 @@ const CustomFields = decorate([
}),
provideState({
effects: {
addCustomField: () => (state, { object: { id } }) => {
const dateTimeUtc = moment.utc()
return form({
component: CustomFieldModal,
defaultValue: {
date: dateTimeUtc.format(DATE_FORMAT),
isDate: false,
name: '',
text: '',
time: dateTimeUtc.format(TIME_FORMAT),
},
header: (
<span>
<Icon icon='add' /> {_('addCustomField')}
</span>
),
}).then(params => checkParamsAndCallMethod(addCustomField, id, params))
},
removeCustomField: (_, { currentTarget: { dataset } }) => (_, { object: { id } }) =>
removeCustomField(id, dataset.name),
setCustomField: (effects, { name, value }) => (state, { object: { id } }) => {
const isDate = PATTERN_DATE_TIME_UTC.test(value)
const dateTimeUtc = isDate ? moment(value).utc() : undefined
return form({
render: props => <CustomFieldModal {...props} update />,
defaultValue: isDate
? {
date: dateTimeUtc.format(DATE_FORMAT),
isDate,
name,
time: dateTimeUtc.format(TIME_FORMAT),
}
: {
isDate,
name,
text: value,
},
header: (
<span>
<Icon icon='edit' /> {_('editCustomField')}
</span>
),
}).then(params => checkParamsAndCallMethod(setCustomField, id, params))
},
addCustomField:
() =>
(state, { object: { id } }) => {
const dateTimeUtc = moment.utc()
return form({
component: CustomFieldModal,
defaultValue: {
date: dateTimeUtc.format(DATE_FORMAT),
isDate: false,
name: '',
text: '',
time: dateTimeUtc.format(TIME_FORMAT),
},
header: (
<span>
<Icon icon='add' /> {_('addCustomField')}
</span>
),
}).then(params => checkParamsAndCallMethod(addCustomField, id, params))
},
removeCustomField:
(_, { currentTarget: { dataset } }) =>
(_, { object: { id } }) =>
removeCustomField(id, dataset.name),
setCustomField:
(effects, { name, value }) =>
(state, { object: { id } }) => {
const isDate = PATTERN_DATE_TIME_UTC.test(value)
const dateTimeUtc = isDate ? moment(value).utc() : undefined
return form({
render: props => <CustomFieldModal {...props} update />,
defaultValue: isDate
? {
date: dateTimeUtc.format(DATE_FORMAT),
isDate,
name,
time: dateTimeUtc.format(TIME_FORMAT),
}
: {
isDate,
name,
text: value,
},
header: (
<span>
<Icon icon='edit' /> {_('editCustomField')}
</span>
),
}).then(params => checkParamsAndCallMethod(setCustomField, id, params))
},
},
computed: {
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
// It only supports native inputs
export const linkState = (_, { target }) => () => ({
[target.name]:
target.nodeName.toLowerCase() === 'input' && target.type.toLowerCase() === 'checkbox'
? target.checked
: target.value,
})
export const linkState =
(_, { target }) =>
() => ({
[target.name]:
target.nodeName.toLowerCase() === 'input' && target.type.toLowerCase() === 'checkbox'
? target.checked
: target.value,
})
export const toggleState = (_, { currentTarget: { name } }) => state => ({
[name]: !state[name],
})
export const toggleState =
(_, { currentTarget: { name } }) =>
state => ({
[name]: !state[name],
})

View File

@ -635,7 +635,10 @@ const renderXoItem = (item, { className, type: xoType, ...props } = {}) => {
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 getObject = createGetObject()

View File

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

View File

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

View File

@ -287,31 +287,33 @@ export const isAdmin = (...args) => {
// Common selector creators.
// Creates an object selector from an id selector.
export const createGetObject = (idSelector = _getId) => (state, props, useResourceSet) => {
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
export const createGetObject =
(idSelector = _getId) =>
(state, props, useResourceSet) => {
const object = state.objects.all[idSelector(state, props)]
if (!object) {
return
}
// predicate is false.
return
}
if (useResourceSet) {
return object
}
if (predicate(object)) {
return object
const predicate = _getPermissionsPredicate(state)
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.
export const createSortForType = invoke(() => {
@ -462,9 +464,12 @@ export const createDoesHostNeedRestart = hostSelector => {
const patchRequiresReboot = createGetObjectsOfType('patch')
.pick(create(hostSelector, host => host.patches))
.find(
create(hostSelector, host => ({ guidance, time, upgrade }) =>
time > host.startTime &&
(upgrade || some(guidance, action => action === 'restartHost' || action === 'restartXapi'))
create(
hostSelector,
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: {
disabled: ({ items }, { disabled, userData }) =>
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),
items: (_, { items, grouped }) => (Array.isArray(items) || !grouped ? items : [items]),
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) => {
if (action.error) {
if (error) {
return error(state, payload, action)
const createAsyncHandler =
({ error, next }) =>
(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.
const combineActionHandlers = invoke(

View File

@ -576,18 +576,20 @@ export const downloadLog = ({ log, date, type }) => {
// ])
// )
// ```
export const createCompare = criterias => (...items) => {
let res = 0
// Array.find to stop when the result is != 0
criterias.find(fn => {
const [v1, v2] = items.map(item => {
const v = typeof fn === 'string' ? item[fn] : fn(item)
return v === true ? -1 : v === false ? 1 : v
export const createCompare =
criterias =>
(...items) => {
let res = 0
// Array.find to stop when the result is != 0
criterias.find(fn => {
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 }),
}).then(() =>
Promise.all(
map(nameLabels, (
name_label, // eslint-disable-line camelcase
i
) =>
_call('vm.create', {
...args,
name_label,
cloudConfig: get(cloudConfigs, i),
})
map(
nameLabels,
(
name_label, // eslint-disable-line camelcase
i
) =>
_call('vm.create', {
...args,
name_label,
cloudConfig: get(cloudConfigs, i),
})
)
)
)

View File

@ -25,7 +25,14 @@ import { ejectCd, isSrWritable, setDefaultSr } from 'xo'
return {
pool: getPool,
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 }

View File

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

View File

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

View File

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

View File

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

View File

@ -93,65 +93,73 @@ const Schedules = decorate([
delete settings[id]
props.handlerSettings(settings)
},
showModal: (effects, { id = generateRandomId(), name, cron, timezone } = DEFAULT_SCHEDULE) => async (
state,
props
) => {
const schedule = get(() => props.schedules[id])
const setting = get(() => props.settings[id])
showModal:
(effects, { id = generateRandomId(), name, cron, timezone } = DEFAULT_SCHEDULE) =>
async (state, props) => {
const schedule = get(() => props.schedules[id])
const setting = get(() => props.settings[id])
const { cron: newCron, name: newName, timezone: newTimezone, ...newSetting } = await form({
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,
const {
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,
},
})
},
...newSetting
} = await form({
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,
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: {
columns: (_, { retentions }) => [...COLUMNS, ...retentions.map(({ defaultValue, ...props }) => props)],
rowTransform: (_, { settings = {}, retentions }) => schedule => {
schedule = { ...schedule, ...settings[schedule.id] }
return setDefaultRetentions(schedule, retentions)
},
rowTransform:
(_, { settings = {}, retentions }) =>
schedule => {
schedule = { ...schedule, ...settings[schedule.id] }
return setDefaultRetentions(schedule, retentions)
},
},
}),
injectState,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -928,14 +928,8 @@ export default class Home extends Component {
} = this.state
const options = OPTIONS[type]
const {
filters,
mainActions,
otherActions,
showHostsSelector,
showPoolsSelector,
showResourceSetsSelector,
} = options
const { filters, mainActions, otherActions, showHostsSelector, showPoolsSelector, showResourceSetsSelector } =
options
return (
<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 getVmController = createGetObjectsOfType('VM-controller').find(
createSelector(getHost, ({ id }) => obj => obj.$container === id)
createSelector(
getHost,
({ id }) =>
obj =>
obj.$container === id
)
)
const getHostVms = createGetObjectsOfType('VM').filter(
createSelector(getHost, ({ id }) => obj => obj.$container === id)
createSelector(
getHost,
({ id }) =>
obj =>
obj.$container === id
)
)
const getNumberOfVms = getHostVms.count()
const getLogs = createGetObjectsOfType('message')
.filter(
createSelector(getHost, getVmController, (host, controller) => ({ $object }) =>
$object === host.id || $object === (controller !== undefined && controller.id)
createSelector(
getHost,
getVmController,
(host, controller) =>
({ $object }) =>
$object === host.id || $object === (controller !== undefined && controller.id)
)
)
.sort()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -260,7 +260,7 @@ export default class NewVm extends BaseComponent {
() => this.props.resourceSets,
createSelector(
() => 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
return pool && pool.id
},
poolId => ({ $pool }) => $pool === poolId
poolId =>
({ $pool }) =>
$pool === poolId
)
_getIsInResourceSet = createSelector(
() => {
@ -592,8 +594,10 @@ export default class NewVm extends BaseComponent {
objectsIds => id => includes(objectsIds, id)
)
_getVmPredicate = createSelector(this._getIsInPool, this._getIsInResourceSet, (isInPool, isInResourceSet) => vm =>
isInResourceSet(vm.id) || isInPool(vm)
_getVmPredicate = createSelector(
this._getIsInPool,
this._getIsInResourceSet,
(isInPool, isInResourceSet) => vm => isInResourceSet(vm.id) || isInPool(vm)
)
_getSrPredicate = createSelector(
this._getIsInPool,

View File

@ -102,7 +102,10 @@ const NewNetwork = decorate([
initialize: async () => ({ bondModes: await getBondModes() }),
linkState,
onChangeMode: (_, bondMode) => ({ bondMode }),
onChangePif: (_, value) => ({ bonded }) => (bonded ? { pifs: value } : { pif: value }),
onChangePif:
(_, value) =>
({ bonded }) =>
bonded ? { pifs: value } : { pif: value },
onChangeEncapsulation(_, encapsulation) {
return { encapsulation: encapsulation.value }
},
@ -144,16 +147,29 @@ const NewNetwork = decorate([
value: mode,
}))
: [],
hostPredicate: ({ networks }, { pool }) => host =>
host.$pool === pool.id || networks.some(({ pool }) => pool !== undefined && pool.id === host.$pool),
pifPredicate: (_, { pool }) => pif => !pif.isBondSlave && pif.vlan === -1 && pif.$host === (pool && pool.master),
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
),
hostPredicate:
({ networks }, { pool }) =>
host =>
host.$pool === pool.id || networks.some(({ pool }) => pool !== undefined && pool.id === host.$pool),
pifPredicate:
(_, { pool }) =>
pif =>
!pif.isBondSlave && pif.vlan === -1 && pif.$host === (pool && pool.master),
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 = [] }) =>
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 getNetworks = createGetObjectsOfType('network')
.filter(createSelector(getPool, ({ id }) => network => network.$pool === id))
.filter(
createSelector(
getPool,
({ id }) =>
network =>
network.$pool === id
)
)
.sort()
const getPifs = createGetObjectsOfType('PIF')
.filter(createSelector(getPool, ({ id }) => pif => pif.$pool === id))
.filter(
createSelector(
getPool,
({ id }) =>
pif =>
pif.$pool === id
)
)
.sort()
const getHosts = createGetObjectsOfType('host')
.filter(createSelector(getPool, ({ id }) => obj => obj.$pool === id))
.filter(
createSelector(
getPool,
({ id }) =>
obj =>
obj.$pool === id
)
)
.sort()
const getPoolSrs = createGetObjectsOfType('SR')
.filter(createSelector(getPool, ({ id }) => sr => sr.$pool === id))
.filter(
createSelector(
getPool,
({ id }) =>
sr =>
sr.$pool === id
)
)
.sort()
const getNumberOfVms = createGetObjectsOfType('VM').count(
createSelector(getPool, ({ id }) => obj => obj.$pool === id)
createSelector(
getPool,
({ id }) =>
obj =>
obj.$pool === id
)
)
const getLogs = createGetObjectMessages(getPool)

View File

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

View File

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

View File

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

View File

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

View File

@ -51,92 +51,96 @@ export default decorate([
setProxy(_, proxy) {
this.state.proxyId = resolveId(proxy)
},
editRemote: ({ reset }) => state => {
const {
remote,
domain = remote.domain || '',
host = remote.host,
name,
options = remote.options || '',
password = remote.password,
port = remote.port,
proxyId = remote.proxy,
type = remote.type,
username = remote.username,
protocol = remote.protocol || 'https',
region = remote.region,
} = state
let { path = remote.path } = state
if (type === 's3') {
const { parsedPath, bucket = parsedPath.split('/')[0], directory = parsedPath.split('/')[1] } = state
path = bucket + '/' + directory
}
return editRemote(remote, {
name,
url: format({
domain,
editRemote:
({ reset }) =>
state => {
const {
remote,
domain = remote.domain || '',
host = remote.host,
name,
options = remote.options || '',
password = remote.password,
port = remote.port,
proxyId = remote.proxy,
type = remote.type,
username = remote.username,
protocol = remote.protocol || 'https',
region = remote.region,
} = state
let { path = remote.path } = state
if (type === 's3') {
const { parsedPath, bucket = parsedPath.split('/')[0], directory = parsedPath.split('/')[1] } = state
path = bucket + '/' + directory
}
return editRemote(remote, {
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,
name,
options,
password,
path,
port: port || undefined,
type,
port,
proxyId,
type = 'nfs',
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>
)
}
} = state
const {
domain = 'WORKGROUP',
host,
name,
options,
password,
path,
port,
proxyId,
type = 'nfs',
username,
} = state
const urlParams = {
host,
path,
port,
type,
}
if (type === 's3') {
const { bucket, directory } = state
urlParams.path = bucket + '/' + directory
}
username && (urlParams.username = username)
password && (urlParams.password = password)
domain && (urlParams.domain = domain)
const urlParams = {
host,
path,
port,
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') {
await confirm({
title: _('localRemoteWarningTitle'),
body: _('localRemoteWarningMessage'),
})
}
if (type === 'file') {
await confirm({
title: _('localRemoteWarningTitle'),
body: _('localRemoteWarningMessage'),
})
}
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)))
},
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 } }) {
this.state.password = value
},

View File

@ -210,16 +210,18 @@ export default class TabGeneral extends Component {
}
)
_getGenerateLink = createSelector(this._getDiskGroups, diskGroups => ids =>
`#/srs/${this.props.sr.id}/disks?s=${encodeURIComponent(
`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')]
_getGenerateLink = createSelector(
this._getDiskGroups,
diskGroups => ids =>
`#/srs/${this.props.sr.id}/disks?s=${encodeURIComponent(
`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()
.join(' ')})`
)}`
.sort()
.join(' ')})`
)}`
)
render() {

View File

@ -79,7 +79,11 @@ const Field = ({ title, children }) => (
})
class Node extends Component {
_replaceBrick = async ({ brick, vm }) => {
const { sr, brickSize, onSameVm = false } = await confirm({
const {
sr,
brickSize,
onSameVm = false,
} = await confirm({
icon: 'refresh',
title: _('xosanReplace'),
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 getSrsContainers = createSelector(getSrs, srs => uniq(map(srs, '$container')))
const getAffinityHostPredicate = createSelector(getSrsContainers, containers => host =>
every(containers, container => container === host.$pool || container === host.id)
const getAffinityHostPredicate = createSelector(
getSrsContainers,
containers => host => every(containers, container => container === host.$pool || container === host.id)
)
return {
@ -275,7 +276,9 @@ class AddAclsModal extends Component {
_getPredicate = createSelector(
() => this.props.acls,
() => 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() {
@ -306,25 +309,29 @@ const Acls = decorate([
}),
provideState({
effects: {
addAcls: () => (state, { acls, vm }) =>
confirm({
title: _('vmAddAcls'),
body: <AddAclsModal acls={acls} vm={vm} />,
})
.then(({ action, subjects }) => {
if (action == null || isEmpty(subjects)) {
return error(_('addAclsErrorTitle'), _('addAclsErrorMessage'))
}
return Promise.all(map(subjects, subject => addAcl({ subject, object: vm, action })))
addAcls:
() =>
(state, { acls, vm }) =>
confirm({
title: _('vmAddAcls'),
body: <AddAclsModal acls={acls} vm={vm} />,
})
.catch(err => err && error(_('addAclsErrorTitle'), err.message || String(err))),
removeAcl: (_, { currentTarget: { dataset } }) => (_, { vm: object }) =>
removeAcl({
action: dataset.action,
object,
subject: dataset.subject,
}),
.then(({ action, subjects }) => {
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))),
removeAcl:
(_, { currentTarget: { dataset } }) =>
(_, { vm: object }) =>
removeAcl({
action: dataset.action,
object,
subject: dataset.subject,
}),
},
computed: {
rawAcls: (_, { acls, vm }) => filter(acls, { object: vm }),

View File

@ -18,7 +18,10 @@ const BackupTab = decorate([
provideState({
computed: {
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,

View File

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

View File

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

View File

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