parent
ec4dde86f5
commit
6d39512576
@ -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> {}
|
||||||
}
|
}
|
||||||
|
@ -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')
|
||||||
|
@ -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/
|
||||||
|
@ -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)
|
||||||
|
@ -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: {
|
||||||
|
@ -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[] }
|
||||||
|
|
||||||
|
@ -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).
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -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) => {
|
||||||
|
@ -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)
|
||||||
|
@ -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'
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
})
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
})
|
})
|
||||||
|
@ -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) {
|
||||||
|
@ -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 }) =>
|
||||||
|
@ -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],
|
||||||
|
})
|
||||||
|
@ -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()
|
||||||
|
@ -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),
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
|
@ -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'))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -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 }
|
||||||
|
@ -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(
|
||||||
|
@ -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) {
|
||||||
|
@ -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 }
|
||||||
)
|
)
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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 ||
|
||||||
|
@ -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,
|
||||||
|
@ -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 = [
|
||||||
{
|
{
|
||||||
|
@ -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) : {}),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -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: [
|
||||||
{
|
{
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
|
@ -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>
|
||||||
|
@ -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()
|
||||||
|
@ -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 }) =>
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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'),
|
||||||
|
@ -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),
|
||||||
|
@ -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,
|
||||||
|
@ -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),
|
||||||
},
|
},
|
||||||
|
@ -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)
|
||||||
|
@ -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(),
|
||||||
|
@ -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),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -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 = () => {
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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() {
|
||||||
|
@ -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} />,
|
||||||
|
@ -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 }),
|
||||||
|
@ -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,
|
||||||
|
@ -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 = [
|
||||||
|
@ -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(
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user