Compare commits

...

45 Commits

Author SHA1 Message Date
Mohamedox
a1c19c92a9 fix(xo-web/ips): bad range formatting
Fixes #3170
2019-05-28 15:17:38 +02:00
HamadaBrest
fdd79885f9 feat(xo-web/VM): display VDI size in migrate modal (#4250)
Fixes #2534
2019-05-27 16:56:45 +02:00
Julien Fontanet
b2eb970796 fix(xo-server/vm.set): cast weight to string
Follow-up of 49e1b0ba7
2019-05-27 16:23:38 +02:00
Julien Fontanet
3ee9c1b550 chore(xo-server/Xapi): remove unused setVcpuWeight 2019-05-27 16:23:37 +02:00
HamadaBrest
2566c24753 fix(xo-web/host): incorrect hypervisor name in RAM usage tooltip (#4248)
Fixes #4246
2019-05-27 15:44:59 +02:00
Julien Fontanet
49e1b0ba7e fix(xo-server/vm.set): cast logical numbers to XAPI string values 2019-05-27 11:10:19 +02:00
Julien Fontanet
453c329f14 fix(xo-server/vm.set): videoram is strictly a number 2019-05-27 11:04:44 +02:00
badrAZ
27193f38f3 feat(xo-web): 5.42.0 2019-05-24 15:32:16 +02:00
badrAZ
d3dc94e210 feat(xo-server): 5.42.0 2019-05-24 15:31:54 +02:00
Julien Fontanet
6dad860635 fix(xo-server/getRemoteHandler): only cache on success
Otherwise subsequent calls will use an invalid handler.

Related to xoa-support#1498
2019-05-23 18:13:58 +02:00
Julien Fontanet
0362ac8909 feat(xo-web/home): case-sensitive filtering 2019-05-23 17:34:19 +02:00
Pierre Donias
e7b79f83d1 fix(xo-web): cast XOA_PLAN for strict equality tests (#4241) 2019-05-23 17:17:48 +02:00
Pierre Donias
62379c1e41 feat(xo-web/settings/logs): suggest XCP-ng when LICENCE_RESTRICTION (#4238)
Fixes #3876
2019-05-23 17:16:39 +02:00
Pierre Donias
23b422e3df feat(xo-server,xo-web/user): forget all authentication tokens (#4224)
Fixes #4214
2019-05-23 17:13:27 +02:00
Pierre Donias
f8e6dee635 fix(xo-web/vm/networks/addresses): avoid duplicate IP addresses (#4239)
Fixes support#1227
2019-05-23 14:33:32 +02:00
Nicolas Raynaud
c8e9b287f4 fix(xo-server/Xapi#_importOvaVm): userdevice must be string not a number (#4232)
Fixes xoa-support#1479
2019-05-22 16:12:19 +02:00
HamadaBrest
c9412dbcd0 fix(xo-web/xoa): ask confirm on upgrade if running jobs (#4235)
Fixes #4164
2019-05-22 15:19:31 +02:00
Julien Fontanet
77222e9e6b Update PULL_REQUEST_TEMPLATE.md 2019-05-22 14:50:57 +02:00
HamadaBrest
9d0f24eae1 feat(xo-web/xoa): release channels support (#4202)
Fixes #4200
2019-05-22 09:32:01 +02:00
Pierre Donias
6e527947be fix(xo-web): adminOnly breaks the routes (#4231)
Introduced by 59e68682bd

`@routes` must always be on top because it decorates add a `routes` property to the component which will be used by the parent.
2019-05-21 17:45:27 +02:00
Julien Fontanet
e7051c1129 fix(xo-server/network.set): dont pass undefined to update_other_config()
Related to 0e1e32d241
2019-05-21 16:47:22 +02:00
Julien Fontanet
3196c7ca09 chore(xo-server): use Xapi's setters (#4229) 2019-05-21 15:44:10 +02:00
Julien Fontanet
0e1e32d241 chore(xo-server): use Xapi's field entries updaters (#4230) 2019-05-21 15:25:37 +02:00
Julien Fontanet
a34912fb0d chore(xo-server): move some calls to Xapi#callAsync (#4227)
Fixes #4226

`.callAsync` is more robust to disconnections than `.call` and should be used for all non-instantaneous calls.

Unfortunately the result can be embedded into XML, you should either not use the result or add `.then(extractOpaquerRef)` if you are expecting an opaque ref.
2019-05-21 15:04:24 +02:00
Dustin B
c7c6e0e2ff chore(xo-web/messages): one shot job → onetime job (#4222) 2019-05-21 11:30:42 +02:00
Julien Fontanet
1e529c995a chore(xo-server/vm.reboot): move into Xapi#rebootVm 2019-05-21 10:37:56 +02:00
Julien Fontanet
7be1c7a47b fix(xo-server): always use Xapi#callAsync for migration 2019-05-21 10:24:14 +02:00
HamadaBrest
b17380443b chore(@xen-orchestra/fs): test truncate() (#4225)
Related to #4180
2019-05-21 10:20:31 +02:00
Pierre Donias
59e68682bd fix(xo-web): lock admin pages (#4220)
Related to xoa-support#1460
2019-05-20 17:31:26 +02:00
Julien Fontanet
b7a92cfe92 feat(xo-server/vif.set): rateLimit support
Server-side of #4215
2019-05-20 16:06:42 +02:00
HamadaBrest
5ebe27da49 feat(fs): add truncate method (#4180) 2019-05-20 14:03:54 +02:00
Julien Fontanet
42df6ba6fa chore: update dependencies 2019-05-17 16:31:39 +02:00
Pierre Donias
8210fddfab fix(xo-web/charts): ensure consistent series order (#4221)
Fixes support#1481
2019-05-16 13:54:27 +02:00
Pierre Donias
f55ed273c5 chore(xo-web): remove unused messages (#4219) 2019-05-16 13:28:39 +02:00
Dustin B
d67e95af7b fix(xo-web/messages): more verbiage and typo fixes, clarifications as well (#4218) 2019-05-15 17:36:00 +02:00
badrAZ
0b0f235252 feat(xo-web/new/metadata): ability to set the backup report when property (#4149) 2019-05-15 16:47:59 +02:00
Jon Sands
36a5f52068 fix(docu/interface) grammar fixes for interface messages (#4213)
* Grammar fixes and typo for messages
2019-05-15 15:10:43 +02:00
Julien Fontanet
31266728f7 feat(xo-server): add / mounts to vendor config 2019-05-15 14:28:20 +02:00
Rajaa.BARHTAOUI
8c79ea4ce3 feat(xo-web/vm/general): display 'Started... ago' for paused state (#4170)
Fixes #3750
2019-05-15 10:41:02 +02:00
Dustin B
c73a4204cb Verbiage change to align with main messages.js 2019-05-14 22:52:58 +02:00
Dustin B
0b3c2cc252 Verbiage changes (#4211)
* Verbiage changes
2019-05-14 22:03:50 +02:00
Dustin B
2bd3ca1d0b Grammar and typos adjustments (#4210)
* Grammar and typos adjustments
2019-05-14 20:55:50 +02:00
badrAZ
ce8649d991 fix(xo-web/backup-ng): handle improper "reportWhen" value (#4199)
Additional change to #4178 to actually fix #4092.
2019-05-14 17:12:53 +02:00
badrAZ
9bd563b111 chore(CHANGELOG): update next 2019-05-14 16:32:31 +02:00
badrAZ
6ceb924a85 feat(xo-web): 5.41.0 2019-05-14 16:20:31 +02:00
68 changed files with 1730 additions and 1597 deletions

View File

@@ -21,12 +21,12 @@
"node": ">=6"
},
"dependencies": {
"@marsaud/smb2": "^0.13.0",
"@marsaud/smb2": "^0.14.0",
"@sindresorhus/df": "^2.1.0",
"@xen-orchestra/async-map": "^0.0.0",
"decorator-synchronized": "^0.5.0",
"execa": "^1.0.0",
"fs-extra": "^7.0.0",
"fs-extra": "^8.0.1",
"get-stream": "^4.0.0",
"lodash": "^4.17.4",
"promise-toolbox": "^0.12.1",
@@ -45,7 +45,7 @@
"async-iterator-to-stream": "^1.1.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"dotenv": "^7.0.0",
"dotenv": "^8.0.0",
"index-modules": "^0.3.0",
"rimraf": "^2.6.2"
},

View File

@@ -400,6 +400,10 @@ export default class RemoteHandlerAbstract {
}
}
async truncate(file: string, len: number): Promise<void> {
await this._truncate(file, len)
}
async unlink(file: string, { checksum = true }: Object = {}): Promise<void> {
file = normalizePath(file)

View File

@@ -350,5 +350,30 @@ handlers.forEach(url => {
}
)
})
describe('#truncate()', () => {
forOwn(
{
'shrinks file': (() => {
const length = random(0, TEST_DATA_LEN)
const expected = TEST_DATA.slice(0, length)
return { length, expected }
})(),
'grows file': (() => {
const length = random(TEST_DATA_LEN, TEST_DATA_LEN * 2)
const expected = Buffer.alloc(length)
TEST_DATA.copy(expected)
return { length, expected }
})(),
},
({ length, expected }, title) => {
it(title, async () => {
await handler.outputFile('file', TEST_DATA)
await handler.truncate('file', length)
await expect(await handler.readFile('file')).toEqual(expected)
})
}
)
})
})
})

View File

@@ -106,6 +106,10 @@ export default class LocalHandler extends RemoteHandlerAbstract {
await fs.access(path, fs.R_OK | fs.W_OK)
}
_truncate(file, len) {
return fs.truncate(this._getFilePath(file), len)
}
async _unlink(file) {
return fs.unlink(this._getFilePath(file))
}

View File

@@ -155,6 +155,12 @@ export default class SmbHandler extends RemoteHandlerAbstract {
return this.list('.')
}
_truncate(file, len) {
return this._client
.truncate(this._getFilePath(file), len)
.catch(normalizeError)
}
_unlink(file) {
return this._client.unlink(this._getFilePath(file)).catch(normalizeError)
}

View File

@@ -1,5 +1,23 @@
# ChangeLog
## **next** (2019-05-14)
### Enhancements
### Bug fixes
- [Pool/Patches] Fix "an error has occurred" in "Applied patches" [#4192](https://github.com/vatesfr/xen-orchestra/issues/4192) (PR [#4193](https://github.com/vatesfr/xen-orchestra/pull/4193))
- [Backup NG] Fix report sent even though "Never" is selected [#4092](https://github.com/vatesfr/xen-orchestra/issues/4092) (PR [#4178](https://github.com/vatesfr/xen-orchestra/pull/4178))
- [Remotes] Fix issues after a config import (PR [#4197](https://github.com/vatesfr/xen-orchestra/pull/4197))
### Released packages
- xo-server-backup-reports v0.16.1
- @xen-orchestra/fs v0.9.0
- vhd-lib v0.7.0
- xo-server v5.41.0
- xo-web v5.41.0
## **5.34.0** (2019-04-30)
### Highlights

View File

@@ -2,16 +2,24 @@
### Enhancements
- [VM/general] Display 'Started... ago' instead of 'Halted... ago' for paused state [#3750](https://github.com/vatesfr/xen-orchestra/issues/3750) (PR [#4170](https://github.com/vatesfr/xen-orchestra/pull/4170))
- [Metadata backup] Ability to define when the backup report will be sent (PR [#4149](https://github.com/vatesfr/xen-orchestra/pull/4149))
- [XOA/Update] Ability to select release channel [#4200](https://github.com/vatesfr/xen-orchestra/issues/4200) (PR [#4202](https://github.com/vatesfr/xen-orchestra/pull/4202))
- [User] Forget connection tokens on password change or on demand [#4214](https://github.com/vatesfr/xen-orchestra/issues/4214) (PR [#4224](https://github.com/vatesfr/xen-orchestra/pull/4224))
- [Settings/Logs] LICENCE_RESTRICTION errors: suggest XCP-ng as an Open Source alternative [#3876](https://github.com/vatesfr/xen-orchestra/issues/3876) (PR [#4238](https://github.com/vatesfr/xen-orchestra/pull/4238))
- [VM/Migrate] Display VDI size on migrate modal [#2534](https://github.com/vatesfr/xen-orchestra/issues/2534) (PR [#4250](https://github.com/vatesfr/xen-orchestra/pull/4250))
### Bug fixes
- [Pool/Patches] Fix "an error has occurred" in "Applied patches" [#4192](https://github.com/vatesfr/xen-orchestra/issues/4192) (PR [#4193](https://github.com/vatesfr/xen-orchestra/pull/4193))
- [Backup NG] Fix report sent even though "Never" is selected [#4092](https://github.com/vatesfr/xen-orchestra/issues/4092) (PR [#4178](https://github.com/vatesfr/xen-orchestra/pull/4178))
- [Remotes] Fix issues after a config import (PR [#4197](https://github.com/vatesfr/xen-orchestra/pull/4197))
- [Charts] Fixed the chart lines sometimes changing order/color (PR [#4221](https://github.com/vatesfr/xen-orchestra/pull/4221))
- Prevent non-admin users to access admin pages with URL
- [Upgrade] Fix alert before upgrade while running backup jobs (PR [#4235](https://github.com/vatesfr/xen-orchestra/pull/4235))
- [Import] Fix import OVA files (PR [#4232](https://github.com/vatesfr/xen-orchestra/pull/4232))
- [VM/network] Fix duplicate IPv4 (PR [#4239](https://github.com/vatesfr/xen-orchestra/pull/4239))
- [Remotes] Fix disconnected remotes which may appear to work
- [Host] Fix incorrect hypervisor name [#4246](https://github.com/vatesfr/xen-orchestra/issues/4246) (PR [#4248](https://github.com/vatesfr/xen-orchestra/pull/4248))
### Released packages
- xo-server-backup-reports v0.16.1
- @xen-orchestra/fs v0.9.0
- vhd-lib v0.7.0
- xo-server v5.41.0
- xo-web v5.41.0
- xo-server v5.42.0
- xo-web v5.42.0

View File

@@ -14,5 +14,5 @@
1. create a PR as soon as possible
1. mark it as `WiP:` (Work in Progress) if not ready to be merged
1. when you want a review, add a reviewer
1. when you want a review, add a reviewer (and only one)
1. if necessary, update your PR, and re- add a reviewer

View File

@@ -12,7 +12,7 @@
"eslint-config-standard-jsx": "^6.0.2",
"eslint-plugin-eslint-comments": "^3.1.1",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-node": "^8.0.0",
"eslint-plugin-node": "^9.0.1",
"eslint-plugin-promise": "^4.0.0",
"eslint-plugin-react": "^7.6.1",
"eslint-plugin-standard": "^4.0.0",

View File

@@ -24,7 +24,7 @@
"async-iterator-to-stream": "^1.0.2",
"core-js": "^3.0.0",
"from2": "^2.3.0",
"fs-extra": "^7.0.0",
"fs-extra": "^8.0.1",
"limit-concurrency-decorator": "^0.4.0",
"promise-toolbox": "^0.12.1",
"struct-fu": "^1.2.0",

View File

@@ -43,7 +43,7 @@
"xo-lib": "^0.9.0"
},
"devDependencies": {
"@types/node": "^11.11.4",
"@types/node": "^12.0.2",
"@types/through2": "^2.0.31",
"tslint": "^5.9.1",
"tslint-config-standard": "^8.0.1",

View File

@@ -74,6 +74,7 @@ honorCipherOrder = true
secureOptions = 117440512
[http.mounts]
'/' = '../xo-web/dist'
[remoteOptions]
mountsDir = '/run/xo-server/mounts'

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "xo-server",
"version": "5.41.0",
"version": "5.42.0",
"license": "AGPL-3.0",
"description": "Server part of Xen-Orchestra",
"keywords": [
@@ -42,7 +42,7 @@
"@xen-orchestra/log": "^0.1.4",
"@xen-orchestra/mixin": "^0.0.0",
"ajv": "^6.1.1",
"app-conf": "^0.6.1",
"app-conf": "^0.7.0",
"archiver": "^3.0.0",
"async-iterator-to-stream": "^1.0.1",
"base64url": "^3.0.0",
@@ -51,7 +51,7 @@
"body-parser": "^1.18.2",
"compression": "^1.7.3",
"connect-flash": "^0.1.1",
"cookie": "^0.3.1",
"cookie": "^0.4.0",
"cookie-parser": "^1.4.3",
"d3-time-format": "^2.1.1",
"debug": "^4.0.1",
@@ -65,7 +65,7 @@
"express-session": "^1.15.6",
"fatfs": "^0.10.4",
"from2": "^2.3.0",
"fs-extra": "^7.0.0",
"fs-extra": "^8.0.1",
"get-stream": "^4.0.0",
"golike-defer": "^0.4.1",
"hashy": "^0.7.1",

View File

@@ -117,7 +117,7 @@ port = 80
# List of files/directories which will be served.
[http.mounts]
#'/' = '/path/to/xo-web/dist/'
#'/any/url' = '/path/to/directory'
# List of proxied URLs (HTTP & WebSockets).
[http.proxies]

View File

@@ -5,7 +5,7 @@
async function delete_({ PBD }) {
// TODO: check if PBD is attached before
await this.getXapi(PBD).call('PBD.destroy', PBD._xapiRef)
await this.getXapi(PBD).callAsync('PBD.destroy', PBD._xapiRef)
}
export { delete_ as delete }
@@ -37,7 +37,7 @@ disconnect.resolve = {
export async function connect({ PBD }) {
// TODO: check if PBD is attached before
await this.getXapi(PBD).call('PBD.plug', PBD._xapiRef)
await this.getXapi(PBD).callAsync('PBD.plug', PBD._xapiRef)
}
connect.params = {

View File

@@ -15,7 +15,7 @@ export function getIpv6ConfigurationModes() {
async function delete_({ pif }) {
// TODO: check if PIF is attached before
await this.getXapi(pif).call('PIF.destroy', pif._xapiRef)
await this.getXapi(pif).callAsync('PIF.destroy', pif._xapiRef)
}
export { delete_ as delete }
@@ -32,7 +32,7 @@ delete_.resolve = {
export async function disconnect({ pif }) {
// TODO: check if PIF is attached before
await this.getXapi(pif).call('PIF.unplug', pif._xapiRef)
await this.getXapi(pif).callAsync('PIF.unplug', pif._xapiRef)
}
disconnect.params = {
@@ -47,7 +47,7 @@ disconnect.resolve = {
export async function connect({ pif }) {
// TODO: check if PIF is attached before
await this.getXapi(pif).call('PIF.plug', pif._xapiRef)
await this.getXapi(pif).callAsync('PIF.plug', pif._xapiRef)
}
connect.params = {

View File

@@ -35,7 +35,7 @@ set.resolve = {
// -------------------------------------------------------------------
export async function scan({ SR }) {
await this.getXapi(SR).call('SR.scan', SR._xapiRef)
await this.getXapi(SR).callAsync('SR.scan', SR._xapiRef)
}
scan.params = {

View File

@@ -34,3 +34,25 @@ delete_.permission = 'admin'
delete_.params = {
token: { type: 'string' },
}
// -------------------------------------------------------------------
export async function deleteAll({ except }) {
await this.deleteAuthenticationTokens({
filter: {
user_id: this.session.get('user_id'),
id: {
__not: except,
},
},
})
}
deleteAll.description =
'delete all tokens of the current user except the current one'
deleteAll.permission = ''
deleteAll.params = {
except: { type: 'string', optional: true },
}

View File

@@ -48,8 +48,7 @@ connect.resolve = {
export async function set({ position, vbd }) {
if (position !== undefined) {
const xapi = this.getXapi(vbd)
await xapi.call('VBD.set_userdevice', vbd._xapiRef, String(position))
await this.getXapiObject(vbd).set_userdevice(String(position))
}
}
@@ -67,9 +66,7 @@ set.resolve = {
// -------------------------------------------------------------------
export async function setBootable({ vbd, bootable }) {
const xapi = this.getXapi(vbd)
await xapi.call('VBD.set_bootable', vbd._xapiRef, bootable)
await this.getXapiObject(vbd).set_bootable(bootable)
}
setBootable.params = {

View File

@@ -64,6 +64,7 @@ export async function set({
allowedIpv4Addresses,
allowedIpv6Addresses,
attached,
rateLimit,
}) {
const oldIpAddresses = vif.allowedIpv4Addresses.concat(
vif.allowedIpv6Addresses
@@ -91,6 +92,9 @@ export async function set({
mac,
currently_attached: attached,
ipv4_allowed: newIpAddresses,
qos_algorithm_type: rateLimit != null ? 'ratelimit' : undefined,
qos_algorithm_params:
rateLimit != null ? { kbps: String(rateLimit) } : undefined,
})
await this.allocIpAddresses(newVif.$id, newIpAddresses)
@@ -107,6 +111,7 @@ export async function set({
return this.getXapi(vif).editVif(vif._xapiId, {
ipv4Allowed: allowedIpv4Addresses,
ipv6Allowed: allowedIpv6Addresses,
rateLimit,
})
}
@@ -129,6 +134,11 @@ set.params = {
optional: true,
},
attached: { type: 'boolean', optional: true },
rateLimit: {
description: 'in kilobytes per seconds',
optional: true,
type: ['number', 'null'],
},
}
set.resolve = {

View File

@@ -603,7 +603,7 @@ set.params = {
// Switch from Cirrus video adaptor to VGA adaptor
vga: { type: 'string', optional: true },
videoram: { type: ['string', 'number'], optional: true },
videoram: { type: 'number', optional: true },
coresPerSocket: { type: ['string', 'number', 'null'], optional: true },
@@ -630,13 +630,7 @@ set.resolve = {
// -------------------------------------------------------------------
export async function restart({ vm, force = false }) {
const xapi = this.getXapi(vm)
if (force) {
await xapi.call('VM.hard_reboot', vm._xapiRef)
} else {
await xapi.call('VM.clean_reboot', vm._xapiRef)
}
return this.getXapi(vm).rebootVm(vm._xapiId, { hard: force })
}
restart.params = {
@@ -737,7 +731,7 @@ export async function convertToTemplate({ vm }) {
// Convert to a template requires pool admin permission.
await this.checkPermissions(this.user.id, [[vm.$pool, 'administrate']])
await this.getXapi(vm).call('VM.set_is_a_template', vm._xapiRef, true)
await this.getXapiObject(vm).set_is_a_template(true)
}
convertToTemplate.params = {
@@ -1089,7 +1083,7 @@ stop.resolve = {
// -------------------------------------------------------------------
export async function suspend({ vm }) {
await this.getXapi(vm).call('VM.suspend', vm._xapiRef)
await this.getXapi(vm).callAsync('VM.suspend', vm._xapiRef)
}
suspend.params = {
@@ -1103,7 +1097,7 @@ suspend.resolve = {
// -------------------------------------------------------------------
export async function pause({ vm }) {
await this.getXapi(vm).call('VM.pause', vm._xapiRef)
await this.getXapi(vm).callAsync('VM.pause', vm._xapiRef)
}
pause.params = {
@@ -1418,15 +1412,11 @@ stats.resolve = {
// -------------------------------------------------------------------
export async function setBootOrder({ vm, order }) {
const xapi = this.getXapi(vm)
order = { order }
if (vm.virtualizationMode === 'hvm') {
await xapi.call('VM.set_HVM_boot_params', vm._xapiRef, order)
return
if (vm.virtualizationMode !== 'hvm') {
throw invalidParameters('You can only set the boot order on a HVM guest')
}
throw invalidParameters('You can only set the boot order on a HVM guest')
await this.getXapiObject(vm).set_HVM_boot_params({ order })
}
setBootOrder.params = {

View File

@@ -269,10 +269,10 @@ export async function fixHostNotInNetwork({ xosanSr, host }) {
if (pif) {
const newIP = _findIPAddressOutsideList(usedAddresses, HOST_FIRST_NUMBER)
reconfigurePifIP(xapi, pif, newIP)
await xapi.call('PIF.plug', pif.$ref)
await xapi.callAsync('PIF.plug', pif.$ref)
const PBD = find(xosanSr.$PBDs, pbd => pbd.$host.$id === host)
if (PBD) {
await xapi.call('PBD.plug', PBD.$ref)
await xapi.callAsync('PBD.plug', PBD.$ref)
}
const sshKey = await getOrCreateSshKey(xapi)
await callPlugin(xapi, host, 'receive_ssh_keys', {
@@ -809,7 +809,7 @@ export const createSR = defer(async function(
})
CURRENT_POOL_OPERATIONS[poolId] = { ...OPERATION_OBJECT, state: 6 }
log.debug('scanning new SR')
await xapi.call('SR.scan', xosanSrRef)
await xapi.callAsync('SR.scan', xosanSrRef)
await this.rebindLicense({
licenseId: license.id,
oldBoundObjectId: tmpBoundObjectId,
@@ -884,7 +884,7 @@ async function createVDIOnLVMWithoutSizeLimit(xapi, lvmSr, diskSize) {
if (result.exit !== 0) {
throw Error('Could not create volume ->' + result.stdout)
}
await xapi.call('SR.scan', xapi.getObject(lvmSr).$ref)
await xapi.callAsync('SR.scan', xapi.getObject(lvmSr).$ref)
const vdi = find(xapi.getObject(lvmSr).$VDIs, vdi => vdi.uuid === uuid)
if (vdi != null) {
await xapi.setSrProperties(vdi.$ref, {
@@ -989,7 +989,7 @@ async function replaceBrickOnSameVM(
await xapi.disconnectVbd(previousVBD)
await xapi.deleteVdi(previousVBD.VDI)
CURRENT_POOL_OPERATIONS[poolId] = { ...OPERATION_OBJECT, state: 4 }
await xapi.call('SR.scan', xapi.getObject(xosansr).$ref)
await xapi.callAsync('SR.scan', xapi.getObject(xosansr).$ref)
} finally {
delete CURRENT_POOL_OPERATIONS[poolId]
}
@@ -1068,7 +1068,7 @@ export async function replaceBrick({
await xapi.deleteVm(previousVMEntry.vm, true)
}
CURRENT_POOL_OPERATIONS[poolId] = { ...OPERATION_OBJECT, state: 3 }
await xapi.call('SR.scan', xapi.getObject(xosansr).$ref)
await xapi.callAsync('SR.scan', xapi.getObject(xosansr).$ref)
} finally {
delete CURRENT_POOL_OPERATIONS[poolId]
}
@@ -1115,7 +1115,7 @@ async function _prepareGlusterVm(
const firstVif = newVM.$VIFs[0]
if (xosanNetwork.$id !== firstVif.$network.$id) {
try {
await xapi.call('VIF.move', firstVif.$ref, xosanNetwork.$ref)
await xapi.callAsync('VIF.move', firstVif.$ref, xosanNetwork.$ref)
} catch (error) {
if (error.code === 'MESSAGE_METHOD_UNKNOWN') {
// VIF.move has been introduced in xenserver 7.0
@@ -1132,7 +1132,7 @@ async function _prepareGlusterVm(
name_description: 'Xosan VM storage',
memory: memorySize,
})
await xapi.call('VM.set_xenstore_data', newVM.$ref, xenstoreData)
await newVM.set_xenstore_data(xenstoreData)
const rootDisk = newVM.$VBDs
.map(vbd => vbd && vbd.$VDI)
.find(vdi => vdi && vdi.name_label === 'xosan_root')
@@ -1330,7 +1330,7 @@ export const addBricks = defer(async function(
data.nodes = data.nodes.concat(newNodes)
await xapi.xo.setData(xosansr, 'xosan_config', data)
CURRENT_POOL_OPERATIONS[poolId] = { ...OPERATION_OBJECT, state: 2 }
await xapi.call('SR.scan', xapi.getObject(xosansr).$ref)
await xapi.callAsync('SR.scan', xapi.getObject(xosansr).$ref)
} finally {
delete CURRENT_POOL_OPERATIONS[poolId]
}
@@ -1382,7 +1382,7 @@ export const removeBricks = defer(async function($defer, { xosansr, bricks }) {
)
remove(data.nodes, node => ips.includes(node.vm.ip))
await xapi.xo.setData(xosansr.id, 'xosan_config', data)
await xapi.call('SR.scan', xapi.getObject(xosansr._xapiId).$ref)
await xapi.callAsync('SR.scan', xapi.getObject(xosansr._xapiId).$ref)
await asyncMap(brickVMs, vm => xapi.deleteVm(vm.vm, true))
} finally {
delete CURRENT_POOL_OPERATIONS[xapi.pool.$id]
@@ -1542,9 +1542,10 @@ export async function downloadAndInstallXosanPack({ id, version, pool }) {
const res = await this.requestResource('xosan', id, version)
await xapi.installSupplementalPackOnAllHosts(res)
await xapi._updateObjectMapProperty(xapi.pool, 'other_config', {
xosan_pack_installation_time: String(Math.floor(Date.now() / 1e3)),
})
await xapi.pool.update_other_config(
'xosan_pack_installation_time',
String(Math.floor(Date.now() / 1e3))
)
}
downloadAndInstallXosanPack.description = 'Register a resource via cloud plugin'

View File

@@ -569,6 +569,16 @@ const TRANSFORMS = {
MAC: obj.MAC,
MTU: +obj.MTU,
// in kB/s
rateLimit: (() => {
if (obj.qos_algorithm_type === 'ratelimit') {
const { kbps } = obj.qos_algorithm_params
if (kbps !== undefined) {
return +kbps
}
}
})(),
$network: link(obj, 'network'),
$VM: link(obj, 'VM'),
}

View File

@@ -29,6 +29,7 @@ import {
groupBy,
includes,
isEmpty,
noop,
omit,
startsWith,
uniq,
@@ -228,14 +229,6 @@ export default class Xapi extends XapiBase {
// =================================================================
_setObjectProperty(object, name, value) {
return this.call(
`${object.$type}.set_${camelToSnakeCase(name)}`,
object.$ref,
prepareXapiParam(value)
)
}
_setObjectProperties(object, props) {
const { $ref: ref, $type: type } = object
@@ -254,57 +247,33 @@ export default class Xapi extends XapiBase {
)::ignoreErrors()
}
async _updateObjectMapProperty(object, prop, values) {
const { $ref: ref, $type: type } = object
prop = camelToSnakeCase(prop)
const add = `${type}.add_to_${prop}`
const remove = `${type}.remove_from_${prop}`
await Promise.all(
mapToArray(values, (value, name) => {
if (value !== undefined) {
name = camelToSnakeCase(name)
const removal = this.call(remove, ref, name)
return value === null
? removal
: removal
::ignoreErrors()
.then(() => this.call(add, ref, name, prepareXapiParam(value)))
}
})
)
}
async setHostProperties(id, { nameLabel, nameDescription }) {
await this._setObjectProperties(this.getObject(id), {
nameLabel,
nameDescription,
})
const host = this.getObject(id)
await Promise.all([
nameDescription !== undefined &&
host.set_name_description(nameDescription),
nameLabel !== undefined && host.set_name_label(nameLabel),
])
}
async setPoolProperties({ autoPoweron, nameLabel, nameDescription }) {
const { pool } = this
await Promise.all([
this._setObjectProperties(pool, {
nameLabel,
nameDescription,
}),
nameDescription !== undefined &&
pool.set_name_description(nameDescription),
nameLabel !== undefined && pool.set_name_label(nameLabel),
autoPoweron != null &&
this._updateObjectMapProperty(pool, 'other_config', {
autoPoweron: autoPoweron ? 'true' : null,
}),
pool.update_other_config('autoPoweron', autoPoweron ? 'true' : null),
])
}
async setSrProperties(id, { nameLabel, nameDescription }) {
await this._setObjectProperties(this.getObject(id), {
nameLabel,
nameDescription,
})
const sr = this.getObject(id)
await Promise.all([
nameDescription !== undefined && sr.set_name_description(nameDescription),
nameLabel !== undefined && sr.set_name_label(nameLabel),
])
}
async setNetworkProperties(
@@ -317,15 +286,13 @@ export default class Xapi extends XapiBase {
}
const network = this.getObject(id)
await Promise.all([
this._setObjectProperties(network, {
defaultLockingMode,
nameDescription,
nameLabel,
}),
this._updateObjectMapProperty(network, 'other_config', {
automatic:
automatic === undefined ? undefined : automatic ? 'true' : null,
}),
defaultLockingMode !== undefined &&
network.set_default_locking_mode(defaultLockingMode),
nameDescription !== undefined &&
network.set_name_description(nameDescription),
nameLabel !== undefined && network.set_name_label(nameLabel),
automatic !== undefined &&
network.update_other_config('automatic', automatic ? 'true' : null),
])
}
@@ -345,10 +312,8 @@ export default class Xapi extends XapiBase {
// =================================================================
async setDefaultSr(srId) {
this._setObjectProperties(this.pool, {
default_SR: this.getObject(srId).$ref,
})
setDefaultSr(srId) {
return this.pool.set_default_SR(this.getObject(srId).$ref)
}
// =================================================================
@@ -377,12 +342,12 @@ export default class Xapi extends XapiBase {
await pSettle(
mapToArray(vms, vm => {
if (!vm.is_control_domain) {
return this.call('VM.suspend', vm.$ref)
return this.callAsync('VM.suspend', vm.$ref)
}
})
)
await this.call('host.disable', host.$ref)
await this.call('host.shutdown', host.$ref)
await this.callAsync('host.shutdown', host.$ref)
}
// =================================================================
@@ -395,7 +360,7 @@ export default class Xapi extends XapiBase {
await this.call('host.disable', ref)
try {
await this.call('host.evacuate', ref)
await this.callAsync('host.evacuate', ref)
} catch (error) {
if (!force) {
await this.call('host.enable', ref)
@@ -410,7 +375,7 @@ export default class Xapi extends XapiBase {
}
async forgetHost(hostId) {
await this.call('host.destroy', this.getObject(hostId).$ref)
await this.callAsync('host.destroy', this.getObject(hostId).$ref)
}
async ejectHostFromPool(hostId) {
@@ -445,9 +410,7 @@ export default class Xapi extends XapiBase {
$defer(() => this.plugPbd(ref))
})
return this._updateObjectMapProperty(
host,
'other_config',
return host.update_other_config(
multipathing
? {
multipathing: 'true',
@@ -460,23 +423,23 @@ export default class Xapi extends XapiBase {
}
async powerOnHost(hostId) {
await this.call('host.power_on', this.getObject(hostId).$ref)
await this.callAsync('host.power_on', this.getObject(hostId).$ref)
}
async rebootHost(hostId, force = false) {
const host = this.getObject(hostId)
await this._clearHost(host, force)
await this.call('host.reboot', host.$ref)
await this.callAsync('host.reboot', host.$ref)
}
async restartHostAgent(hostId) {
await this.call('host.restart_agent', this.getObject(hostId).$ref)
await this.callAsync('host.restart_agent', this.getObject(hostId).$ref)
}
async setRemoteSyslogHost(hostId, syslogDestination) {
const host = this.getObject(hostId)
await this.call('host.set_logging', host.$ref, {
await host.set_logging({
syslog_destination: syslogDestination,
})
await this.call('host.syslog_reconfigure', host.$ref)
@@ -486,7 +449,7 @@ export default class Xapi extends XapiBase {
const host = this.getObject(hostId)
await this._clearHost(host, force)
await this.call('host.shutdown', host.$ref)
await this.callAsync('host.shutdown', host.$ref)
}
// =================================================================
@@ -500,7 +463,7 @@ export default class Xapi extends XapiBase {
}`
)
return this.call('VM.clone', vm.$ref, nameLabel)
return this.callAsync('VM.clone', vm.$ref, nameLabel).then(extractOpaqueRef)
}
// Copy a VM: make a normal copy of a VM and all its VDIs.
@@ -571,12 +534,7 @@ export default class Xapi extends XapiBase {
stream = stream.pipe(sizeStream)
const onVmCreation =
nameLabel !== undefined
? vm =>
targetXapi._setObjectProperties(vm, {
nameLabel,
})
: null
nameLabel !== undefined ? vm => vm.set_name_label(nameLabel) : null
const vm = await targetXapi._getOrWaitObject(
await targetXapi._importVm(stream, sr, onVmCreation)
@@ -716,17 +674,13 @@ export default class Xapi extends XapiBase {
// It is necessary for suspended VMs to be shut down
// to be able to delete their VDIs.
if (vm.power_state !== 'Halted') {
await this.call('VM.hard_shutdown', $ref)
await this.callAsync('VM.hard_shutdown', $ref)
}
await Promise.all([
this.call('VM.set_is_a_template', vm.$ref, false),
this._updateObjectMapProperty(vm, 'blocked_operations', {
destroy: null,
}),
this._updateObjectMapProperty(vm, 'other_config', {
default_template: null,
}),
vm.set_is_a_template(false),
vm.update_blocked_operations('destroy', null),
vm.update_other_config('default_template', null),
])
// must be done before destroying the VM
@@ -734,7 +688,7 @@ export default class Xapi extends XapiBase {
// this cannot be done in parallel, otherwise disks and snapshots will be
// destroyed even if this fails
await this.call('VM.destroy', $ref)
await this.callAsync('VM.destroy', $ref)
return Promise.all([
asyncMap(vm.$snapshots, snapshot =>
@@ -1071,15 +1025,9 @@ export default class Xapi extends XapiBase {
$defer.onFailure(() => this._deleteVm(vm))
await Promise.all([
this._setObjectProperties(vm, {
name_label: `[Importing…] ${name_label}`,
}),
this._updateObjectMapProperty(vm, 'blocked_operations', {
start: 'Importing…',
}),
this._updateObjectMapProperty(vm, 'other_config', {
[TAG_COPY_SRC]: delta.vm.uuid,
}),
vm.set_name_label(`[Importing…] ${name_label}`),
vm.update_blocked_operations('start', 'Importing…'),
vm.update_other_config(TAG_COPY_SRC, delta.vm.uuid),
])
// 2. Delete all VBDs which may have been created by the import.
@@ -1103,9 +1051,7 @@ export default class Xapi extends XapiBase {
newVdi = await this._getOrWaitObject(await this._cloneVdi(baseVdi))
$defer.onFailure(() => this._deleteVdi(newVdi.$ref))
await this._updateObjectMapProperty(newVdi, 'other_config', {
[TAG_COPY_SRC]: vdi.uuid,
})
await newVdi.update_other_config(TAG_COPY_SRC, vdi.uuid)
} else {
newVdi = await this.createVdi({
...vdi,
@@ -1200,15 +1146,14 @@ export default class Xapi extends XapiBase {
}
await Promise.all([
this._setObjectProperties(vm, {
name_label,
}),
vm.set_name_label(name_label),
// FIXME: move
this._updateObjectMapProperty(vm, 'blocked_operations', {
start: disableStartAfterImport
vm.update_blocked_operations(
'start',
disableStartAfterImport
? 'Do not start this VM, clone it if you want to use it.'
: null,
}),
: null
),
])
return { transferSize, vm }
@@ -1262,7 +1207,7 @@ export default class Xapi extends XapiBase {
)
const loop = () =>
this.call(
this.callAsync(
'VM.migrate_send',
vm.$ref,
token,
@@ -1276,7 +1221,7 @@ export default class Xapi extends XapiBase {
pDelay(1e4).then(loop)
)
return loop()
return loop().then(noop)
}
@synchronized()
@@ -1436,9 +1381,7 @@ export default class Xapi extends XapiBase {
'start',
'OVA import in progress...'
),
this._setObjectProperties(vm, {
name_label: `[Importing...] ${nameLabel}`,
}),
vm.set_name_label(`[Importing...] ${nameLabel}`),
])
// 2. Create VDIs & Vifs.
@@ -1455,7 +1398,7 @@ export default class Xapi extends XapiBase {
$defer.onFailure(() => this._deleteVdi(vdi.$ref))
return this.createVbd({
userdevice: disk.position,
userdevice: String(disk.position),
vdi,
vm,
})
@@ -1499,7 +1442,7 @@ export default class Xapi extends XapiBase {
// Enable start and restore the VM name label after import.
await Promise.all([
this.removeForbiddenOperationFromVm(vm.$id, 'start'),
this._setObjectProperties(vm, { name_label: nameLabel }),
vm.set_name_label(nameLabel),
])
return vm
}
@@ -1546,7 +1489,7 @@ export default class Xapi extends XapiBase {
})
} else {
try {
await this.call('VM.pool_migrate', vm.$ref, host.$ref, {
await this.callAsync('VM.pool_migrate', vm.$ref, host.$ref, {
force: 'true',
})
} catch (error) {
@@ -1631,19 +1574,11 @@ export default class Xapi extends XapiBase {
return /* await */ this._snapshotVm(this.getObject(vmId), nameLabel)
}
async setVcpuWeight(vmId, weight) {
weight = weight || null // Take all falsy values as a removal (0 included)
const vm = this.getObject(vmId)
await this._updateObjectMapProperty(vm, 'VCPUs_params', { weight })
}
async _startVm(vm, host, force) {
log.debug(`Starting VM ${vm.name_label}`)
if (force) {
await this._updateObjectMapProperty(vm, 'blocked_operations', {
start: null,
})
await vm.update_blocked_operations('start', null)
}
return host === undefined
@@ -1653,7 +1588,7 @@ export default class Xapi extends XapiBase {
false, // Start paused?
false // Skip pre-boot checks?
)
: this.call('VM.start_on', vm.$ref, host.$ref, false, false)
: this.callAsync('VM.start_on', vm.$ref, host.$ref, false, false)
}
async startVm(vmId, hostId, force) {
@@ -1682,16 +1617,12 @@ export default class Xapi extends XapiBase {
if (isVmHvm(vm)) {
const { order } = vm.HVM_boot_params
await this._updateObjectMapProperty(vm, 'HVM_boot_params', {
order: 'd',
})
await vm.update_HVM_boot_params('order', 'd')
try {
await this._startVm(vm)
} finally {
await this._updateObjectMapProperty(vm, 'HVM_boot_params', {
order,
})
await vm.update_HVM_boot_params('order', order)
}
} else {
// Find the original template by name (*sigh*).
@@ -1713,20 +1644,14 @@ export default class Xapi extends XapiBase {
const cdDrive = this._getVmCdDrive(vm)
forEach(vm.$VBDs, vbd => {
promises.push(
this._setObjectProperties(vbd, {
bootable: vbd === cdDrive,
})
)
promises.push(vbd.set_bootable(vbd === cdDrive))
bootables.push([vbd, Boolean(vbd.bootable)])
})
promises.push(
this._setObjectProperties(vm, {
PV_bootloader: 'eliloader',
}),
this._updateObjectMapProperty(vm, 'other_config', {
vm.set_PV_bootloader('eliloader'),
vm.update_other_config({
'install-distro':
template && template.other_config['install-distro'],
'install-repository': 'cdrom',
@@ -1737,12 +1662,10 @@ export default class Xapi extends XapiBase {
await this._startVm(vm)
} finally {
this._setObjectProperties(vm, {
PV_bootloader: bootloader,
})::ignoreErrors()
vm.set_PV_bootloader(bootloader)::ignoreErrors()
forEach(bootables, ([vbd, bootable]) => {
this._setObjectProperties(vbd, { bootable })::ignoreErrors()
vbd.set_bootable(bootable)::ignoreErrors()
})
}
}
@@ -1826,14 +1749,14 @@ export default class Xapi extends XapiBase {
})
if (isVmRunning(vm)) {
await this.call('VBD.plug', vbdRef)
await this.callAsync('VBD.plug', vbdRef)
}
}
_cloneVdi(vdi) {
log.debug(`Cloning VDI ${vdi.name_label}`)
return this.call('VDI.clone', vdi.$ref)
return this.callAsync('VDI.clone', vdi.$ref).then(extractOpaqueRef)
}
async createVdi({
@@ -1856,7 +1779,7 @@ export default class Xapi extends XapiBase {
log.debug(`Creating VDI ${name_label} on ${sr.name_label}`)
return this._getOrWaitObject(
await this.call('VDI.create', {
await this.callAsync('VDI.create', {
name_description,
name_label,
other_config,
@@ -1868,7 +1791,7 @@ export default class Xapi extends XapiBase {
type,
virtual_size: size !== undefined ? parseSize(size) : virtual_size,
xenstore_data,
})
}).then(extractOpaqueRef)
)
}
@@ -1886,9 +1809,12 @@ export default class Xapi extends XapiBase {
}`
)
try {
await pRetry(() => this.call('VDI.pool_migrate', vdi.$ref, sr.$ref, {}), {
when: { code: 'TOO_MANY_STORAGE_MIGRATES' },
})
await pRetry(
() => this.callAsync('VDI.pool_migrate', vdi.$ref, sr.$ref, {}),
{
when: { code: 'TOO_MANY_STORAGE_MIGRATES' },
}
)
} catch (error) {
const { code } = error
if (
@@ -1899,7 +1825,9 @@ export default class Xapi extends XapiBase {
throw error
}
const newVdi = await this.barrier(
await this.call('VDI.copy', vdi.$ref, sr.$ref)
await this.callAsync('VDI.copy', vdi.$ref, sr.$ref).then(
extractOpaqueRef
)
)
await asyncMap(vdi.$VBDs, async vbd => {
await this.call('VBD.destroy', vbd.$ref)
@@ -1917,7 +1845,7 @@ export default class Xapi extends XapiBase {
log.debug(`Deleting VDI ${vdiRef}`)
try {
await this.call('VDI.destroy', vdiRef)
await this.callAsync('VDI.destroy', vdiRef)
} catch (error) {
if (error?.code !== 'HANDLE_INVALID') {
throw error
@@ -1930,7 +1858,7 @@ export default class Xapi extends XapiBase {
`Resizing VDI ${vdi.name_label} from ${vdi.virtual_size} to ${size}`
)
return this.call('VDI.resize', vdi.$ref, size)
return this.callAsync('VDI.resize', vdi.$ref, size)
}
_getVmCdDrive(vm) {
@@ -1944,7 +1872,7 @@ export default class Xapi extends XapiBase {
async _ejectCdFromVm(vm) {
const cdDrive = this._getVmCdDrive(vm)
if (cdDrive) {
await this.call('VBD.eject', cdDrive.$ref)
await this.callAsync('VBD.eject', cdDrive.$ref)
}
}
@@ -1952,20 +1880,20 @@ export default class Xapi extends XapiBase {
const cdDrive = await this._getVmCdDrive(vm)
if (cdDrive) {
try {
await this.call('VBD.insert', cdDrive.$ref, cd.$ref)
await this.callAsync('VBD.insert', cdDrive.$ref, cd.$ref)
} catch (error) {
if (!force || error.code !== 'VBD_NOT_EMPTY') {
throw error
}
await this.call('VBD.eject', cdDrive.$ref)::ignoreErrors()
await this.callAsync('VBD.eject', cdDrive.$ref)::ignoreErrors()
// Retry.
await this.call('VBD.insert', cdDrive.$ref, cd.$ref)
await this.callAsync('VBD.insert', cdDrive.$ref, cd.$ref)
}
if (bootable !== Boolean(cdDrive.bootable)) {
await this._setObjectProperties(cdDrive, { bootable })
await cdDrive.set_bootable(bootable)
}
} else {
await this.createVbd({
@@ -1978,7 +1906,7 @@ export default class Xapi extends XapiBase {
}
async connectVbd(vbdId) {
await this.call('VBD.plug', vbdId)
await this.callAsync('VBD.plug', vbdId)
}
async _disconnectVbd(vbd) {
@@ -1987,7 +1915,7 @@ export default class Xapi extends XapiBase {
await this.call('VBD.unplug_force', vbd.$ref)
} catch (error) {
if (error.code === 'VBD_NOT_UNPLUGGABLE') {
await this.call('VBD.set_unpluggable', vbd.$ref, true)
await vbd.set_unpluggable(true)
return this.call('VBD.unplug_force', vbd.$ref)
}
}
@@ -2038,11 +1966,11 @@ export default class Xapi extends XapiBase {
const vdi = this.getObject(vdiId)
const snap = await this._getOrWaitObject(
await this.call('VDI.snapshot', vdi.$ref)
await this.callAsync('VDI.snapshot', vdi.$ref).then(extractOpaqueRef)
)
if (nameLabel) {
await this.call('VDI.set_name_label', snap.$ref, nameLabel)
await snap.set_name_label(nameLabel)
}
return snap
@@ -2166,7 +2094,7 @@ export default class Xapi extends XapiBase {
)
if (currently_attached && isVmRunning(vm)) {
await this.call('VIF.plug', vifRef)
await this.callAsync('VIF.plug', vifRef)
}
return vifRef
@@ -2194,7 +2122,7 @@ export default class Xapi extends XapiBase {
// https://citrix.github.io/xenserver-sdk/#network
other_config: { automatic: 'false' },
})
$defer.onFailure(() => this.call('network.destroy', networkRef))
$defer.onFailure(() => this.callAsync('network.destroy', networkRef))
if (pifId) {
await this.call(
'pool.create_VLAN_from_PIF',
@@ -2233,7 +2161,7 @@ export default class Xapi extends XapiBase {
await Promise.all(
mapToArray(
vlans,
vlan => vlan !== NULL_REF && this.call('VLAN.destroy', vlan)
vlan => vlan !== NULL_REF && this.callAsync('VLAN.destroy', vlan)
)
)
@@ -2248,7 +2176,7 @@ export default class Xapi extends XapiBase {
newPifs,
pifRef =>
!wasAttached[this.getObject(pifRef).host] &&
this.call('PIF.unplug', pifRef)::ignoreErrors()
this.callAsync('PIF.unplug', pifRef)::ignoreErrors()
)
)
}
@@ -2279,7 +2207,7 @@ export default class Xapi extends XapiBase {
await Promise.all(
mapToArray(
vlans,
vlan => vlan !== NULL_REF && this.call('VLAN.destroy', vlan)
vlan => vlan !== NULL_REF && this.callAsync('VLAN.destroy', vlan)
)
)
@@ -2288,7 +2216,7 @@ export default class Xapi extends XapiBase {
mapToArray(bonds, bond => this.call('Bond.destroy', bond))
)
await this.call('network.destroy', network.$ref)
await this.callAsync('network.destroy', network.$ref)
}
// =================================================================

View File

@@ -68,11 +68,6 @@ declare export class Xapi {
sr?: XapiObject,
onVmCreation?: (XapiObject) => any
): Promise<string>;
_updateObjectMapProperty(
object: XapiObject,
property: string,
entries: $Dict<null | string>
): Promise<void>;
_setObjectProperties(
object: XapiObject,
properties: $Dict<mixed>

View File

@@ -4,13 +4,13 @@ import { makeEditObject } from '../utils'
export default {
async _connectVif(vif) {
await this.call('VIF.plug', vif.$ref)
await this.callAsync('VIF.plug', vif.$ref)
},
async connectVif(vifId) {
await this._connectVif(this.getObject(vifId))
},
async _deleteVif(vif) {
await this.call('VIF.destroy', vif.$ref)
await this.callAsync('VIF.destroy', vif.$ref)
},
async deleteVif(vifId) {
const vif = this.getObject(vifId)
@@ -20,7 +20,7 @@ export default {
await this._deleteVif(vif)
},
async _disconnectVif(vif) {
await this.call('VIF.unplug_force', vif.$ref)
await this.callAsync('VIF.unplug_force', vif.$ref)
},
async disconnectVif(vifId) {
await this._disconnectVif(this.getObject(vifId))
@@ -37,7 +37,7 @@ export default {
: 'locked'
if (lockingMode !== vif.locking_mode) {
return this._set('locking_mode', lockingMode)
return vif.set_locking_mode(lockingMode)
}
},
],
@@ -53,10 +53,36 @@ export default {
: 'locked'
if (lockingMode !== vif.locking_mode) {
return this._set('locking_mode', lockingMode)
return vif.set_locking_mode(lockingMode)
}
},
],
},
// in kB/s
rateLimit: {
get: vif => {
if (vif.qos_algorithm_type === 'ratelimit') {
const { kbps } = vif.qos_algorithm_params
if (kbps !== undefined) {
return +kbps
}
}
// null is value used to remove the existing value
//
// we need to match this, to allow avoiding the `set` if the value is
// already missing.
return null
},
set: (value, vif) =>
Promise.all([
vif.set_qos_algorithm_type(value === null ? '' : 'ratelimit'),
vif.update_qos_algorithm_params(
'kbps',
value === null ? null : String(value)
),
]),
},
}),
}

View File

@@ -353,9 +353,10 @@ export default {
if (JSON.parse(update).exit !== 0) {
throw new Error('Update install failed')
} else {
await this._updateObjectMapProperty(host, 'other_config', {
rpm_patch_installation_time: String(Date.now() / 1000),
})
await host.update_other_config(
'rpm_patch_installation_time',
String(Date.now() / 1000)
)
}
})
},

View File

@@ -35,7 +35,7 @@ export default {
},
_plugPbd(pbd) {
return this.call('PBD.plug', pbd.$ref)
return this.callAsync('PBD.plug', pbd.$ref)
},
async plugPbd(id) {
@@ -43,7 +43,7 @@ export default {
},
_unplugPbd(pbd) {
return this.call('PBD.unplug', pbd.$ref)
return this.callAsync('PBD.unplug', pbd.$ref)
},
async unplugPbd(id) {

View File

@@ -94,7 +94,7 @@ export default {
// Creates the VDIs and executes the initial steps of the
// installation.
await this.call('VM.provision', vmRef)
await this.callAsync('VM.provision', vmRef)
let vm = await this._getOrWaitObject(vmRef)
@@ -115,9 +115,7 @@ export default {
order = 'ncd'
}
this._setObjectProperties(vm, {
HVM_boot_params: { ...bootParams, order },
})
vm.set_HVM_boot_params({ ...bootParams, order })
}
} else {
// PV
@@ -125,13 +123,12 @@ export default {
if (installMethod === 'network') {
// TODO: normalize RHEL URL?
await this._updateObjectMapProperty(vm, 'other_config', {
'install-repository': installRepository,
})
await vm.update_other_config(
'install-repository',
installRepository
)
} else if (installMethod === 'cd') {
await this._updateObjectMapProperty(vm, 'other_config', {
'install-repository': 'cdrom',
})
await vm.update_other_config('install-repository', 'cdrom')
}
}
}
@@ -264,21 +261,14 @@ export default {
_editVm: makeEditObject({
affinityHost: {
get: 'affinity',
set(value, vm) {
return this._setObjectProperty(
vm,
'affinity',
value ? this.getObject(value).$ref : NULL_REF
)
},
set: (value, vm) =>
vm.set_affinity(value ? this.getObject(value).$ref : NULL_REF),
},
autoPoweron: {
set(value, vm) {
return Promise.all([
this._updateObjectMapProperty(vm, 'other_config', {
autoPoweron: value ? 'true' : null,
}),
vm.update_other_config('autoPoweron', value ? 'true' : null),
value &&
this.setPoolProperties({
autoPoweron: true,
@@ -292,23 +282,19 @@ export default {
if (virtualizationMode !== 'pv' && virtualizationMode !== 'hvm') {
throw new Error(`The virtualization mode must be 'pv' or 'hvm'`)
}
return this._set('domain_type', virtualizationMode)::pCatch(
{ code: 'MESSAGE_METHOD_UNKNOWN' },
() =>
this._set(
'HVM_boot_policy',
return vm
.set_domain_type(virtualizationMode)
::pCatch({ code: 'MESSAGE_METHOD_UNKNOWN' }, () =>
vm.set_HVM_boot_policy(
virtualizationMode === 'hvm' ? 'Boot order' : ''
)
)
)
},
},
coresPerSocket: {
set(coresPerSocket, vm) {
return this._updateObjectMapProperty(vm, 'platform', {
'cores-per-socket': coresPerSocket,
})
},
set: (coresPerSocket, vm) =>
vm.update_platform('cores-per-socket', String(coresPerSocket)),
},
CPUs: 'cpus',
@@ -326,26 +312,22 @@ export default {
get: vm => +vm.VCPUs_at_startup,
set: [
'VCPUs_at_startup',
function(value, vm) {
return isVmRunning(vm) && this._set('VCPUs_number_live', value)
},
(value, vm) => isVmRunning(vm) && vm.set_VCPUs_number_live(value),
],
},
cpuCap: {
get: vm => vm.VCPUs_params.cap && +vm.VCPUs_params.cap,
set(cap, vm) {
return this._updateObjectMapProperty(vm, 'VCPUs_params', { cap })
},
set: (cap, vm) => vm.update_VCPUs_params('cap', String(cap)),
},
cpuMask: {
get: vm => vm.VCPUs_params.mask && vm.VCPUs_params.mask.split(','),
set(cpuMask, vm) {
return this._updateObjectMapProperty(vm, 'VCPUs_params', {
mask: cpuMask == null ? cpuMask : cpuMask.join(','),
})
},
set: (cpuMask, vm) =>
vm.update_VCPUs_params(
'mask',
cpuMask == null ? cpuMask : cpuMask.join(',')
),
},
cpusMax: 'cpusStaticMax',
@@ -359,15 +341,15 @@ export default {
cpuWeight: {
get: vm => vm.VCPUs_params.weight && +vm.VCPUs_params.weight,
set(weight, vm) {
return this._updateObjectMapProperty(vm, 'VCPUs_params', { weight })
},
set: (weight, vm) =>
vm.update_VCPUs_params(
'weight',
weight === null ? null : String(weight)
),
},
highAvailability: {
set(ha, vm) {
return this.call('VM.set_ha_restart_priority', vm.$ref, ha)
},
set: (ha, vm) => vm.set_ha_restart_priority(ha),
},
memoryMin: {
@@ -439,19 +421,12 @@ export default {
hasVendorDevice: true,
expNestedHvm: {
set(expNestedHvm, vm) {
return this._updateObjectMapProperty(vm, 'platform', {
'exp-nested-hvm': expNestedHvm ? 'true' : null,
})
},
set: (expNestedHvm, vm) =>
vm.update_platform('exp-nested-hvm', expNestedHvm ? 'true' : null),
},
nicType: {
set(nicType, vm) {
return this._updateObjectMapProperty(vm, 'platform', {
nic_type: nicType,
})
},
set: (nicType, vm) => vm.update_platform('nic_type', nicType),
},
vga: {
@@ -461,7 +436,7 @@ export default {
`The different values that the VGA can take are: ${XEN_VGA_VALUES}`
)
}
return this._updateObjectMapProperty(vm, 'platform', { vga })
return vm.update_platform('vga', vga)
},
},
@@ -472,15 +447,13 @@ export default {
`The different values that the video RAM can take are: ${XEN_VIDEORAM_VALUES}`
)
}
return this._updateObjectMapProperty(vm, 'platform', { videoram })
return vm.update_platform('videoram', String(videoram))
},
},
startDelay: {
get: vm => +vm.start_delay,
set(startDelay, vm) {
return this.call('VM.set_start_delay', vm.$ref, startDelay)
},
set: (startDelay, vm) => vm.set_start_delay(startDelay),
},
}),
@@ -493,7 +466,7 @@ export default {
if (snapshotBefore) {
await this._snapshotVm(snapshot.$snapshot_of)
}
await this.call('VM.revert', snapshot.$ref)
await this.callAsync('VM.revert', snapshot.$ref)
if (snapshot.snapshot_info['power-state-at-snapshot'] === 'Running') {
const vm = await this.barrier(snapshot.snapshot_of)
if (vm.power_state === 'Halted') {
@@ -506,15 +479,22 @@ export default {
async resumeVm(vmId) {
// the force parameter is always true
return this.call('VM.resume', this.getObject(vmId).$ref, false, true)
await this.callAsync('VM.resume', this.getObject(vmId).$ref, false, true)
},
async unpauseVm(vmId) {
return this.call('VM.unpause', this.getObject(vmId).$ref)
await this.callAsync('VM.unpause', this.getObject(vmId).$ref)
},
rebootVm(vmId, { hard = false } = {}) {
return this.callAsync(
`VM.${hard ? 'hard' : 'clean'}_reboot`,
this.getObject(vmId).$ref
).then(noop)
},
shutdownVm(vmId, { hard = false } = {}) {
return this.call(
return this.callAsync(
`VM.${hard ? 'hard' : 'clean'}_shutdown`,
this.getObject(vmId).$ref
).then(noop)

View File

@@ -148,8 +148,8 @@ export const makeEditObject = specs => {
if (set === true) {
const prop = camelToSnakeCase(name)
return function(value) {
return this._set(prop, value)
return function(value, obj) {
return this.setField(obj.$type, obj.$ref, prop, value)
}
}
@@ -157,16 +157,22 @@ export const makeEditObject = specs => {
const index = set.indexOf('.')
if (index === -1) {
const prop = camelToSnakeCase(set)
return function(value) {
return this._set(prop, value)
return function(value, obj) {
return this.setField(obj.$type, obj.$ref, prop, value)
}
}
const map = set.slice(0, index)
const prop = set.slice(index + 1)
const field = set.slice(0, index)
const entry = set.slice(index + 1)
return function(value, object) {
return this._updateObjectMapProperty(object, map, { [prop]: value })
return this.setFieldEntry(
object.$type,
object.$ref,
field,
entry,
value
)
}
}
@@ -249,16 +255,6 @@ export const makeEditObject = specs => {
const limits = checkLimits && {}
const object = this.getObject(id)
const _objectRef = object.$ref
const _setMethodPrefix = `${object.$type}.set_`
// Context used to execute functions.
const context = {
__proto__: this,
_set: (prop, value) =>
this.call(_setMethodPrefix + prop, _objectRef, prepareXapiParam(value)),
}
const set = (value, name) => {
if (value === undefined) {
return
@@ -287,7 +283,7 @@ export const makeEditObject = specs => {
}
}
const cb = () => spec.set.call(context, value, object)
const cb = () => spec.set.call(this, value, object)
const { constraints } = spec
if (constraints) {

View File

@@ -1,4 +1,5 @@
import createLogger from '@xen-orchestra/log'
import { createPredicate } from 'value-matcher'
import { ignoreErrors } from 'promise-toolbox'
import { invalidCredentials, noSuchObject } from 'xo-common/api-errors'
@@ -193,6 +194,14 @@ export default class {
}
}
async deleteAuthenticationTokens({ filter }) {
return Promise.all(
(await this._tokens.get())
.filter(createPredicate(filter))
.map(({ id }) => this.deleteAuthenticationToken(id))
)
}
async getAuthenticationToken(id) {
let token = await this._tokens.first(id)
if (token === undefined) {

View File

@@ -449,9 +449,7 @@ const disableVmHighAvailability = async (xapi: Xapi, vm: Vm) => {
}
return Promise.all([
xapi._setObjectProperties(vm, {
haRestartPriority: '',
}),
vm.set_ha_restart_priority(''),
xapi.addTag(vm.$ref, 'HA disabled'),
])
}
@@ -937,7 +935,7 @@ export default class BackupNg {
message: 'clean backup metadata on VM',
parentId: taskId,
},
xapi._updateObjectMapProperty(vm, 'other_config', {
vm.update_other_config({
'xo:backup:datetime': null,
'xo:backup:deltaChainLength': null,
'xo:backup:exported': null,
@@ -1051,7 +1049,7 @@ export default class BackupNg {
message: 'add metadata to snapshot',
parentId: taskId,
},
xapi._updateObjectMapProperty(snapshot, 'other_config', {
snapshot.update_other_config({
'xo:backup:datetime': snapshot.snapshot_time,
'xo:backup:job': jobId,
'xo:backup:schedule': scheduleId,
@@ -1258,11 +1256,11 @@ export default class BackupNg {
result: () => ({ size: xva.size }),
},
xapi._importVm($cancelToken, fork, sr, vm =>
xapi._setObjectProperties(vm, {
nameLabel: `${metadata.vm.name_label} - ${
vm.set_name_label(
`${metadata.vm.name_label} - ${
job.name
} - (${safeDateFormat(metadata.timestamp)})`,
})
} - (${safeDateFormat(metadata.timestamp)})`
)
)
)
)
@@ -1270,13 +1268,11 @@ export default class BackupNg {
await Promise.all([
xapi.addTag(vm.$ref, 'Disaster Recovery'),
disableVmHighAvailability(xapi, vm),
xapi._updateObjectMapProperty(vm, 'blocked_operations', {
start:
'Start operation for this vm is blocked, clone it if you want to use it.',
}),
xapi._updateObjectMapProperty(vm, 'other_config', {
'xo:backup:sr': srId,
}),
vm.update_blocked_operations(
'start',
'Start operation for this vm is blocked, clone it if you want to use it.'
),
vm.update_other_config('xo:backup:sr', srId),
])
if (!deleteFirst) {
@@ -1630,13 +1626,11 @@ export default class BackupNg {
await Promise.all([
xapi.addTag(vm.$ref, 'Continuous Replication'),
disableVmHighAvailability(xapi, vm),
xapi._updateObjectMapProperty(vm, 'blocked_operations', {
start:
'Start operation for this vm is blocked, clone it if you want to use it.',
}),
xapi._updateObjectMapProperty(vm, 'other_config', {
'xo:backup:sr': srId,
}),
vm.update_blocked_operations(
'start',
'Start operation for this vm is blocked, clone it if you want to use it.'
),
vm.update_other_config('xo:backup:sr', srId),
])
if (!deleteFirst) {
@@ -1667,9 +1661,7 @@ export default class BackupNg {
message: 'set snapshot.other_config[xo:backup:exported]',
parentId: taskId,
},
xapi._updateObjectMapProperty(snapshot, 'other_config', {
'xo:backup:exported': 'true',
})
snapshot.update_other_config('xo:backup:exported', 'true')
)
}

View File

@@ -456,11 +456,9 @@ export default class {
// (Asynchronously) Identify snapshot as future base.
promise
.then(() => {
return srcXapi._updateObjectMapProperty(srcVm, 'other_config', {
[TAG_LAST_BASE_DELTA]: delta.vm.uuid,
})
})
.then(() =>
srcVm.update_other_config(TAG_LAST_BASE_DELTA, delta.vm.uuid)
)
::ignoreErrors()
return promise
@@ -974,10 +972,10 @@ export default class {
nameLabel: copyName,
})
targetXapi._updateObjectMapProperty(data.vm, 'blocked_operations', {
start:
'Start operation for this vm is blocked, clone it if you want to use it.',
})
data.vm.update_blocked_operations(
'start',
'Start operation for this vm is blocked, clone it if you want to use it.'
)
await targetXapi.addTag(data.vm.$id, 'Disaster Recovery')

View File

@@ -67,7 +67,7 @@ export default class {
const handlers = this._handlers
let handler = handlers[id]
if (handler === undefined) {
handler = handlers[id] = getHandler(remote, this._remoteOptions)
handler = getHandler(remote, this._remoteOptions)
try {
await handler.sync()
@@ -76,6 +76,8 @@ export default class {
ignoreErrors.call(this._updateRemote(id, { error: error.message }))
throw error
}
handlers[id] = handler
}
return handler

View File

@@ -386,14 +386,12 @@ export default class {
return value && JSON.parse(value)
},
setData: async (id, key, value) => {
await xapi._updateObjectMapProperty(
xapi.getObject(id),
'other_config',
{
[`xo:${camelToSnakeCase(key)}`]:
value !== null ? JSON.stringify(value) : value,
}
)
await xapi
.getObject(id)
.update_other_config(
`xo:${camelToSnakeCase(key)}`,
value !== null ? JSON.stringify(value) : value
)
// Register the updated object.
addObject(await xapi._waitObject(id))
@@ -457,6 +455,11 @@ export default class {
return xapi
}
// returns the XAPI object corresponding to an XO object
getXapiObject(xoObject) {
return this.getXapi(xoObject).getObjectByRef(xoObject._xapiRef)
}
_getXenServerStatus(id) {
const xapi = this._xapis[id]
return xapi === undefined

View File

@@ -39,7 +39,7 @@
"cross-env": "^5.1.3",
"event-to-promise": "^0.8.0",
"execa": "^1.0.0",
"fs-extra": "^7.0.0",
"fs-extra": "^8.0.1",
"get-stream": "^4.0.0",
"index-modules": "^0.3.0",
"rimraf": "^2.6.2"

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "xo-web",
"version": "5.40.1",
"version": "5.42.0",
"license": "AGPL-3.0",
"description": "Web interface client for Xen-Orchestra",
"keywords": [
@@ -85,7 +85,7 @@
"immutable": "^4.0.0-rc.9",
"index-modules": "^0.3.0",
"is-ip": "^2.0.0",
"jsonrpc-websocket-client": "^0.4.1",
"jsonrpc-websocket-client": "^0.5.0",
"kindof": "^2.0.0",
"lodash": "^4.6.1",
"loose-envify": "^1.1.0",

View File

@@ -228,7 +228,7 @@ export default {
userPage: 'Utilisateur',
// Original text: "No support"
noSupport: 'Pas de support',
noSupport: 'Pas de support professionnel',
// Original text: "Free upgrade!"
freeUpgrade: 'Mise à jour gratuite !',

View File

@@ -11,8 +11,8 @@ const messages = {
statusDisconnected: 'Disconnected',
statusLoading: 'Loading…',
errorPageNotFound: 'Page not found',
errorNoSuchItem: 'no such item',
errorUnknownItem: 'unknown {type}',
errorNoSuchItem: 'No such item',
errorUnknownItem: 'Unknown {type}',
memoryFree: '{memoryFree} RAM free',
date: 'Date',
notifications: 'Notifications',
@@ -32,8 +32,8 @@ const messages = {
multipathingDisabled: 'Multipathing disabled',
enableMultipathing: 'Enable multipathing',
disableMultipathing: 'Disable multipathing',
enableAllHostsMultipathing: 'Enable all hosts multipathing',
disableAllHostsMultipathing: 'Disable all hosts multipathing',
enableAllHostsMultipathing: 'Enable multipathing for all hosts',
disableAllHostsMultipathing: 'Disable multipathing for all hosts',
paths: 'Paths',
pbdDisconnected: 'PBD disconnected',
hasInactivePath: 'Has an inactive path',
@@ -166,8 +166,8 @@ const messages = {
homeHelp: 'Want some help?',
homeAddServer: 'Add server',
homeConnectServer: 'Connect servers',
homeOnlineDoc: 'Online Doc',
homeProSupport: 'Pro Support',
homeOnlineDoc: 'Online doc',
homeProSupport: 'Pro support',
homeNoVms: 'There are no VMs!',
homeNoVmsOr: 'Or…',
homeImportVm: 'Import VM',
@@ -206,7 +206,6 @@ const messages = {
homeSortBySize: 'Size',
homeSortByType: 'Type',
homeSortByUsage: 'Usage',
homeSortByvCPUs: 'vCPUs',
homeSortVmsBySnapshots: 'Snapshots',
homeSortByContainer: 'Container',
homeSortByPool: 'Pool',
@@ -219,9 +218,8 @@ const messages = {
homeResourceSet: 'Resource set: {resourceSet}',
highAvailability: 'High Availability',
srSharedType: 'Shared {type}',
srNotSharedType: 'Not shared {type}',
// ----- Home snapshots -----
// ----- Home snapshots -----
snapshotVmsName: 'Name',
snapshotVmsDescription: 'Description',
@@ -266,14 +264,14 @@ const messages = {
item: 'Item',
noSelectedValue: 'No selected value',
selectSubjects: 'Choose user(s) and/or group(s)',
selectObjects: 'Select Object(s)…',
selectObjects: 'Select object(s)…',
selectRole: 'Choose a role',
selectHosts: 'Select Host(s)…',
selectHosts: 'Select host(s)…',
selectHostsVms: 'Select object(s)…',
selectNetworks: 'Select Network(s)…',
selectNetworks: 'Select network(s)…',
selectPifs: 'Select PIF(s)…',
selectPools: 'Select Pool(s)…',
selectRemotes: 'Select Remote(s)…',
selectPools: 'Select pool(s)…',
selectRemotes: 'Select remote(s)…',
selectResourceSets: 'Select resource set(s)…',
selectResourceSetsVmTemplate: 'Select template(s)…',
selectResourceSetsSr: 'Select SR(s)…',
@@ -290,10 +288,9 @@ const messages = {
selectIp: 'Select IP(s)…',
selectIpPool: 'Select IP pool(s)…',
selectVgpuType: 'Select VGPU type(s)…',
fillRequiredInformations: 'Fill required information.',
fillOptionalInformations: 'Fill information (optional)',
selectTableReset: 'Reset',
selectCloudConfigs: 'Select Cloud Config(s)…',
selectCloudConfigs: 'Select cloud config(s)…',
// --- Dates/Scheduler ---
@@ -307,13 +304,10 @@ const messages = {
selectTableAllDay: 'Every day',
selectTableAllHour: 'Every hour',
selectTableAllMinute: 'Every minute',
schedulingReset: 'Reset',
unknownSchedule: 'Unknown',
timezonePickerUseLocalTime: 'Web browser timezone',
serverTimezoneOption: 'Server timezone ({value})',
cronPattern: 'Cron Pattern:',
backupEditNotFoundTitle: 'Cannot edit backup',
backupEditNotFoundMessage: 'Missing required info for edition',
successfulJobCall: 'Successful',
failedJobCall: 'Failed',
jobCallSkipped: 'Skipped',
@@ -345,7 +339,7 @@ const messages = {
runJob: 'Run job',
cancelJob: 'Cancel job',
runJobConfirm: 'Are you sure you want to run {backupType} {id} ({tag})?',
runJobVerbose: 'One shot running started. See overview for logs.',
runJobVerbose: 'Onetime job started. See overview for logs.',
jobEdit: 'Edit job',
jobDelete: 'Delete',
jobFinished: 'Finished',
@@ -376,7 +370,7 @@ const messages = {
deleteSelectedJobs: 'Delete selected jobs',
scheduleEnableAfterCreation: 'Enable immediately after creation',
scheduleEditMessage:
'You are editing Schedule {name} ({id}). Saving will override previous schedule state.',
'You are editing schedule {name} ({id}). Saving will override previous schedule state.',
jobEditMessage:
'You are editing job {name} ({id}). Saving will override previous job state.',
scheduleEdit: 'Edit schedule',
@@ -390,7 +384,7 @@ const messages = {
missingRetentions:
'The modes need at least a schedule with retention higher than 0',
missingExportRetention:
'The Backup mode and The Delta Backup mode require backup retention to be higher than 0!',
'The Backup mode and the Delta Backup mode require backup retention to be higher than 0!',
missingCopyRetention:
'The CR mode and The DR mode require replication retention to be higher than 0!',
missingSnapshotRetention:
@@ -410,25 +404,24 @@ const messages = {
noScheduledJobs: 'No scheduled jobs.',
legacySnapshotsLink: 'You can delete all your legacy backup snapshots.',
newSchedule: 'New schedule',
noJobs: 'No jobs found.',
noSchedules: 'No schedules found',
jobActionPlaceHolder: 'Select a xo-server API command',
jobActionPlaceHolder: 'Select an xo-server API command',
jobTimeoutPlaceHolder:
'Timeout (number of seconds after which a VM is considered failed)',
jobSchedules: 'Schedules',
jobScheduleNamePlaceHolder: 'Name of your schedule',
jobScheduleJobPlaceHolder: 'Select a Job',
jobScheduleJobPlaceHolder: 'Select a job',
jobOwnerPlaceholder: 'Job owner',
jobUserNotFound: "This job's creator no longer exists",
backupUserNotFound: "This backup's creator no longer exists",
redirectToMatchingVms: 'Click here to see the matching VMs',
migrateToBackupNg: 'Migrate to backup NG',
migrateToBackupNg: 'Migrate to Backup NG',
noMatchingVms: 'There are no matching VMs!',
allMatchingVms: '{icon} See the matching VMs ({nMatchingVms, number})',
backupOwner: 'Backup owner',
migrateBackupSchedule: 'Migrate to backup NG',
migrateBackupSchedule: 'Migrate to Backup NG',
migrateBackupScheduleMessage:
'This will migrate this backup to a backup NG. This operation is not reversible. Do you want to continue?',
'This will convert the old backup job to a Backup NG job. This operation is not reversible. Do you want to continue?',
runBackupNgJobConfirm: 'Are you sure you want to run {name} ({id})?',
cancelJobConfirm: 'Are you sure you want to cancel {name} ({id})?',
scheduleDstWarning:
@@ -448,38 +441,38 @@ const messages = {
smartBackup: 'Smart backup',
snapshotRetention: 'Snapshot retention',
backupName: 'Name',
useDelta: 'Use delta',
offlineSnapshot: 'Offline snapshot',
offlineSnapshotInfo: 'Shutdown VMs before snapshotting them',
timeout: 'Timeout',
timeoutInfo: 'Number of hours after which a job is considered failed',
fullBackupInterval: 'Full backup interval',
timeoutUnit: 'in hours',
dbAndDrRequireEnterprisePlan: 'Delta Backup and DR require Enterprise plan',
crRequiresPremiumPlan: 'CR requires Premium plan',
timeoutUnit: 'In hours',
dbAndDrRequireEnterprisePlan:
'Delta Backup and DR require an Enterprise plan',
crRequiresPremiumPlan: 'CR requires a Premium plan',
smartBackupModeTitle: 'Smart mode',
backupTargetRemotes: 'Target remotes (for Export)',
backupTargetSrs: 'Target SRs (for Replication)',
backupTargetRemotes: 'Target remotes (for export)',
backupTargetSrs: 'Target SRs (for replication)',
localRemoteWarningTitle: 'Local remote selected',
crOnThickProvisionedSrWarning:
'Tip: using a thin-provisioned storage will consume less space. Please click on the icon to get more information',
'Tip: Using thin-provisioned storage will consume less space. Please click on the icon to get more information',
vmsOnThinProvisionedSrTip:
'Tip: creating VMs on a thin-provisioned storage will consume less space when backing them up. Please click on the icon to get more information',
'Tip: Creating VMs on thin-provisioned storage will consume less space. Please click on the icon to get more information',
deltaBackupOnOutdatedXenServerWarning:
'Delta Backup and Continuous Replication require at least XenServer 6.5.',
backupNgLinkToDocumentationMessage:
'Click for more information about the backup modes.',
'Click for more information about the backup methods.',
localRemoteWarningMessage:
'Warning: local remotes will use limited XOA disk space. Only for advanced users.',
'Warning: Local remotes will use limited XOA disk space. Only for advanced users.',
backupVersionWarning:
'Warning: this feature works only with XenServer 6.5 or newer.',
'Warning: This feature works only with XenServer 6.5 or newer.',
editBackupVmsTitle: 'VMs',
editBackupSmartStatusTitle: 'VMs statuses',
editBackupSmartResidentOn: 'Resident on',
editBackupSmartNotResidentOn: 'Not resident on',
editBackupSmartPools: 'Pools',
editBackupSmartTags: 'Tags',
sampleOfMatchingVms: 'Sample of matching Vms',
sampleOfMatchingVms: 'Sample of matching VMs',
backupReplicatedVmsInfo:
'Replicated VMs (VMs with Continuous Replication or Disaster Recovery tag) must be excluded!',
editBackupSmartTagsTitle: 'VMs Tags',
@@ -495,21 +488,19 @@ const messages = {
'Delete old backups before backing up the VMs. If the new backup fails, you will lose your old backups.',
// ------ New Remote -----
remoteList: 'Remote stores for backup',
newRemote: 'New File System Remote',
newRemote: 'New file system remote',
remoteTypeLocal: 'Local',
remoteTypeNfs: 'NFS',
remoteTypeSmb: 'SMB',
remoteType: 'Type',
remoteSmbWarningMessage:
'SMB remotes are meant to work on Windows Server. For other systems (Linux Samba, which means almost all NAS), please use NFS.',
'SMB remotes are meant to work with Windows Server. For other systems (Linux Samba, which means almost all NAS), please use NFS.',
remoteTestTip: 'Test your remote',
testRemote: 'Test Remote',
testRemote: 'Test remote',
remoteTestFailure: 'Test failed for {name}',
remoteTestSuccess: 'Test passed for {name}',
remoteTestError: 'Error',
remoteTestStep: 'Test Step',
remoteTestFile: 'Test file',
remoteTestStep: 'Test step',
remoteTestName: 'Test name',
remoteTestNameFailure: 'Remote name already exists!',
remoteTestSuccessMessage: 'The remote appears to work correctly',
@@ -532,33 +523,29 @@ const messages = {
remoteSpeedInfo: 'Read and write rate speed performed during latest test',
remoteOptions: 'Options',
remoteShare: 'Share',
remoteAction: 'Action',
remoteAuth: 'Auth',
remoteMounted: 'Mounted',
remoteUnmounted: 'Unmounted',
remoteDeleteTip: 'Delete',
remoteDeleteSelected: 'Delete selected remotes',
remoteNamePlaceHolder: 'remote name *',
remoteMyNamePlaceHolder: 'Name *',
remoteLocalPlaceHolderPath: '/path/to/backup',
remoteNfsPlaceHolderHost: 'host *',
remoteNfsPlaceHolderHost: 'Host *',
remoteNfsPlaceHolderPort: 'Port',
remoteNfsPlaceHolderPath: 'path/to/backup',
remoteNfsPlaceHolderOptions: 'Custom mount options. Default: vers=3',
remoteSmbPlaceHolderRemotePath: 'subfolder [path\\\\to\\\\backup]',
remoteSmbPlaceHolderRemotePath: 'Subfolder [path\\\\to\\\\backup]',
remoteSmbPlaceHolderUsername: 'Username',
remoteSmbPlaceHolderPassword: 'Password',
remoteSmbPlaceHolderDomain: 'Domain',
remoteSmbPlaceHolderAddressShare: '<address>\\\\<share> *',
remoteSmbPlaceHolderOptions: 'Custom mount options',
remotePlaceHolderPassword: 'password(fill to edit)',
remotePlaceHolderPassword: 'Password(fill to edit)',
// ------ New Storage -----
newSrTitle: 'Create a new SR',
newSrGeneral: 'General',
newSrTypeSelection: 'Select Storage Type:',
newSrTypeSelection: 'Select storage type:',
newSrSettings: 'Settings',
newSrUsage: 'Storage Usage',
newSrUsage: 'Storage usage',
newSrSummary: 'Summary',
newSrHost: 'Host',
newSrType: 'Type',
@@ -569,11 +556,11 @@ const messages = {
newSrIqn: 'IQN',
newSrLun: 'LUN',
newSrNoHba: 'No HBA devices',
newSrAuth: 'with auth.',
newSrUsername: 'User Name',
newSrAuth: 'With auth.',
newSrUsername: 'User name',
newSrPassword: 'Password',
newSrDevice: 'Device',
newSrInUse: 'in use',
newSrInUse: 'In use',
newSrSize: 'Size',
newSrCreate: 'Create',
newSrNamePlaceHolder: 'Storage name',
@@ -588,34 +575,34 @@ const messages = {
newSrNfsOptions: 'Comma delimited NFS options',
reattachNewSrTooltip: 'Reattach SR',
// ------ New Newtork -----
createNewNetworkNoPermission: 'You have no permission to create a network',
// ------ New Network -----
createNewNetworkNoPermission:
'You do not have permission to create a network',
createNewNetworkOn: 'Create a new network on {select}',
// ----- Acls, Users, Groups ------
// ----- ACLs, Users, Groups ------
subjectName: 'Users/Groups',
objectName: 'Object',
aclNoneFound: 'No acls found',
roleName: 'Role',
aclCreate: 'Create',
newGroupName: 'New Group Name',
createGroup: 'Create Group',
newGroupName: 'New group name',
createGroup: 'Create group',
createGroupButton: 'Create',
deleteGroup: 'Delete Group',
deleteGroup: 'Delete group',
deleteGroupConfirm: 'Are you sure you want to delete this group?',
removeUserFromGroup: 'Remove user from Group',
removeUserFromGroup: 'Remove user from group',
deleteUserConfirm: 'Are you sure you want to delete this user?',
deleteUser: 'Delete user',
deleteSelectedUsers: 'Delete selected users',
deleteUsersModalTitle: 'Delete user{nUsers, plural, one {} other {s}}',
deleteUsersModalMessage:
'Are you sure you want to delete {nUsers, number} user{nUsers, plural, one {} other {s}}?',
noUser: 'no user',
unknownUser: 'unknown user',
noUser: 'No user',
unknownUser: 'Unknown user',
noGroupFound: 'No group found',
groupNameColumn: 'Name',
groupUsersColumn: 'Users',
addUserToGroupColumn: 'Add User',
addUserToGroupColumn: 'Add user',
userNameColumn: 'Username',
userGroupsColumn: 'Member of',
userCountGroups: '{nGroups, number} group{nGroups, plural, one {} other {s}}',
@@ -629,7 +616,7 @@ const messages = {
adminLabel: 'Admin',
noUserInGroup: 'No user in group',
countUsers: '{users, number} user{users, plural, one {} other {s}}',
selectPermission: 'Select Permission',
selectPermission: 'Select permission',
deleteAcl: 'Delete ACL',
deleteSelectedAcls: 'Delete selected ACLs',
deleteAclsModalTitle: 'Delete ACL{nAcls, plural, one {} other {s}}',
@@ -645,8 +632,7 @@ const messages = {
unknownPluginError: 'Unknown error',
purgePluginConfiguration: 'Purge plugin configuration',
purgePluginConfigurationQuestion:
'Are you sure you want to purge this configuration ?',
editPluginConfiguration: 'Edit',
'Are you sure you want to purge this configuration?',
cancelPluginEdition: 'Cancel',
pluginConfigurationSuccess: 'Plugin configuration',
pluginConfigurationChanges: 'Plugin configuration successfully saved!',
@@ -660,15 +646,12 @@ const messages = {
filterName: 'Name:',
filterValue: 'Value:',
saveNewFilterTitle: 'Save new filter',
setUserFiltersTitle: 'Set custom filters',
setUserFiltersBody: 'Are you sure you want to set custom filters?',
removeUserFilterTitle: 'Remove custom filter',
removeUserFilterBody: 'Are you sure you want to remove custom filter?',
removeUserFilterBody: 'Are you sure you want to remove the custom filter?',
defaultFilter: 'Default filter',
defaultFilters: 'Default filters',
customFilters: 'Custom filters',
customizeFilters: 'Customize filters',
saveCustomFilters: 'Save custom filters',
// ----- VM actions ------
startVmLabel: 'Start',
@@ -691,7 +674,6 @@ const messages = {
copyVmLabel: 'Copy',
cloneVmLabel: 'Clone',
fastCloneVmLabel: 'Fast clone',
convertVmToTemplateLabel: 'Convert to template',
vmConsoleLabel: 'Console',
// ----- SR advanced tab -----
@@ -727,19 +709,18 @@ const messages = {
displayAllVMs: 'Display all VMs of this pool',
licenseRestrictions: 'License restrictions',
licenseRestrictionsModalTitle:
'Warning: you are using a Free XenServer license',
actionsRestricted: 'Some actions will be restricted.',
'Warning: You are using a Free XenServer license',
actionsRestricted: 'Some functionality is restricted.',
counterRestrictionsOptions: 'You can:',
counterRestrictionsOptionsXcp:
'upgrade to XCP-ng for free to get rid of these restrictions',
counterRestrictionsOptionsXsLicense: 'or get a commercial Citrix license',
// ----- Pool tabs -----
hostsTabName: 'Hosts',
vmsTabName: 'Vms',
srsTabName: 'Srs',
vmsTabName: 'VMs',
srsTabName: 'SRs',
// ----- Pool advanced tab -----
poolEditAll: 'Edit all',
poolEditRemoteSyslog: 'Edit remote syslog for all hosts',
poolHaStatus: 'High Availability',
poolHaEnabled: 'Enabled',
poolHaDisabled: 'Disabled',
@@ -750,7 +731,6 @@ const messages = {
// ----- Pool host tab -----
hostNameLabel: 'Name',
hostDescription: 'Description',
hostMemory: 'Memory',
noHost: 'No hosts',
memoryLeftTooltip: '{used}% used ({free} free)',
// ----- Pool network tab -----
@@ -767,8 +747,6 @@ const messages = {
poolNetworkPifDetached: 'Disconnected',
showPifs: 'Show PIFs',
hidePifs: 'Hide PIFs',
showDetails: 'Show details',
hideDetails: 'Hide details',
// ----- Pool stats tab -----
poolNoStats: 'No stats',
poolAllHosts: 'All hosts',
@@ -777,9 +755,9 @@ const messages = {
addVmLabel: 'Add VM',
addHostLabel: 'Add Host',
missingPatchesPool:
'The pool needs to install {nMissingPatches, number} patch{nMissingPatches, plural, one {} other {es}}. This operation may be long.',
'The pool needs to install {nMissingPatches, number} patch{nMissingPatches, plural, one {} other {es}}. This operation may take a while.',
missingPatchesHost:
'This host needs to install {nMissingPatches, number} patch{nMissingPatches, plural, one {} other {es}}. This operation may be long.',
'This host needs to install {nMissingPatches, number} patch{nMissingPatches, plural, one {} other {es}}. This operation may take a while.',
patchUpdateNoInstall:
'This host cannot be added to the pool because the patches are not homogeneous.',
addHostErrorTitle: 'Adding host failed',
@@ -796,7 +774,7 @@ const messages = {
rebootHostLabel: 'Reboot',
noHostsAvailableErrorTitle: 'Error while restarting host',
noHostsAvailableErrorMessage:
'Some VMs cannot be migrated before restarting this host. Please try force reboot.',
'Some VMs cannot be migrated without first rebooting this host. Please try force reboot.',
failHostBulkRestartTitle: 'Error while restarting hosts',
failHostBulkRestartMessage:
'{failedHosts, number}/{totalHosts, number} host{failedHosts, plural, one {} other {s}} could not be restarted.',
@@ -821,9 +799,9 @@ const messages = {
hostMultipathingPaths:
'{nActives, number} of {nPaths, number} path{nPaths, plural, one {} other {s}} ({ nSessions, number } iSCSI session{nSessions, plural, one {} other {s}})',
hostMultipathingRequiredState:
'This action will not be fulfilled if a VM is in a running state. Please ensure that all VMs are evacuated or stopped before doing this action!',
'This action will not be fulfilled if a VM is in a running state. Please ensure that all VMs are evacuated or stopped before performing this action!',
hostMultipathingWarning:
'The host{nHosts, plural, one {} other {s}} will lose the connection to the SRs. Do you want to continue?',
'The host{nHosts, plural, one {} other {s}} will lose their connection to the SRs. Do you want to continue?',
hostXenServerVersion: 'Version',
hostStatusEnabled: 'Enabled',
hostStatusDisabled: 'Disabled',
@@ -854,7 +832,6 @@ const messages = {
'Supplemental pack successfully installed.',
// ----- Host net tabs -----
networkCreateButton: 'Add a network',
networkCreateBondedButton: 'Add a bonded network',
pifDeviceLabel: 'Device',
pifNetworkLabel: 'Network',
pifVlanLabel: 'VLAN',
@@ -864,11 +841,7 @@ const messages = {
pifMtuLabel: 'MTU',
pifSpeedLabel: 'Speed',
pifStatusLabel: 'Status',
pifStatusConnected: 'Connected',
pifStatusDisconnected: 'Disconnected',
pifNoInterface: 'No physical interface detected',
pifInUse: 'This interface is currently in use',
pifAction: 'Action',
defaultLockingMode: 'Default locking mode',
pifConfigureIp: 'Configure IP address',
configIpErrorTitle: 'Invalid parameters',
@@ -879,9 +852,7 @@ const messages = {
gateway: 'Gateway',
// ----- Host storage tabs -----
addSrDeviceButton: 'Add a storage',
srNameLabel: 'Name',
srType: 'Type',
pbdAction: 'Action',
pbdStatus: 'Status',
pbdStatusConnected: 'Connected',
pbdStatusDisconnected: 'Disconnected',
@@ -898,13 +869,9 @@ const messages = {
patchVersion: 'Version',
patchApplied: 'Applied date',
patchSize: 'Size',
patchStatus: 'Status',
patchStatusApplied: 'Applied',
patchStatusNotApplied: 'Missing patches',
patchNothing: 'No patches detected',
patchReleaseDate: 'Release date',
patchGuidance: 'Guidance',
patchAction: 'Action',
hostAppliedPatches: 'Applied patches',
hostMissingPatches: 'Missing patches',
hostUpToDate: 'Host up-to-date!',
@@ -923,7 +890,6 @@ const messages = {
changelogDate: 'Date',
changelogDescription: 'Description',
// ----- Pool patch tabs -----
refreshPatches: 'Refresh patches',
install: 'Install',
installPatchesTitle: 'Install patch{nPatches, plural, one {} other {es}}',
installPatchesContent:
@@ -932,7 +898,6 @@ const messages = {
confirmPoolPatch:
'Are you sure you want to install all the patches on this pool?',
poolNeedsDefaultSr: 'The pool needs a default SR to install the patches.',
selectDefaultSr: 'Select a default SR',
vmsHaveCds:
'{nVms, number} VM{nVms, plural, one {} other {s}} {nVms, plural, one {has} other {have}} CDs',
ejectCds: 'Eject CDs',
@@ -952,10 +917,10 @@ const messages = {
networkTabName: 'Network',
disksTabName: 'Disk{disks, plural, one {} other {s}}',
powerStateHalted: 'halted',
powerStateRunning: 'running',
powerStateSuspended: 'suspended',
powerStatePaused: 'paused',
powerStateHalted: 'Halted',
powerStateRunning: 'Running',
powerStateSuspended: 'Suspended',
powerStatePaused: 'Paused',
// ----- VM home -----
vmCurrentStatus: 'Current status:',
@@ -989,8 +954,8 @@ const messages = {
copyToClipboardLabel: 'Copy',
ctrlAltDelButtonLabel: 'Ctrl+Alt+Del',
tipLabel: 'Tip:',
hideHeaderTooltip: 'Hide infos',
showHeaderTooltip: 'Show infos',
hideHeaderTooltip: 'Hide info',
showHeaderTooltip: 'Show info',
// ----- VM container tab -----
containerName: 'Name',
@@ -1006,14 +971,12 @@ const messages = {
containerRestart: 'Restart this container',
// ----- VM disk tab -----
vdiAction: 'Action',
vdiAttachDeviceButton: 'Attach disk',
vbdCreateDeviceButton: 'New disk',
vdiBootOrder: 'Boot order',
vdiNameLabel: 'Name',
vdiNameDescription: 'Description',
vdiPool: 'Pool',
vdiDisconnect: 'Disconnect',
vdiTags: 'Tags',
vdiSize: 'Size',
vdiSr: 'SR',
@@ -1025,18 +988,16 @@ const messages = {
vdiMigrateNoSrMessage: 'A target SR is required to migrate a VDI',
vdiForget: 'Forget',
vdiRemove: 'Remove VDI',
noControlDomainVdis: 'No VDIs attached to Control Domain',
noControlDomainVdis: 'No VDIs attached to control domain',
vbdBootableStatus: 'Boot flag',
vbdDevice: 'Device',
vbdStatus: 'Status',
vbdStatusConnected: 'Connected',
vbdStatusDisconnected: 'Disconnected',
vbdNoVbd: 'No disks',
vbdConnect: 'Connect VBD',
vbdDisconnect: 'Disconnect VBD',
vbdBootable: 'Bootable',
vbdReadonly: 'Readonly',
vbdAction: 'Action',
vbdCreate: 'Create',
vbdAttach: 'Attach',
vbdNamePlaceHolder: 'Disk name',
@@ -1056,11 +1017,10 @@ const messages = {
notEnoughSpaceInResourceSet:
'Not enough space in resource set {resourceSet} ({spaceLeft} left)',
warningVdiSr:
"The VDIs' SRs must either be shared or on the same host for the VM to be able to start.",
"The VDIs' SRs must either be shared or on the same host for the VM to be able to start.",
// ----- VM network tab -----
vifCreateDeviceButton: 'New device',
vifNoInterface: 'No interface',
vifDeviceLabel: 'Device',
vifMacLabel: 'MAC address',
vifMtuLabel: 'MTU',
@@ -1081,13 +1041,12 @@ const messages = {
'Network locked and no IPs are allowed for this interface',
vifUnLockedNetwork: 'Network not locked',
vifUnknownNetwork: 'Unknown network',
vifAction: 'Action',
vifCreate: 'Create',
// ----- VM snapshot tab -----
noSnapshots: 'No snapshots',
newSnapshotWithMemory: 'New snapshot with memory',
snapshotMemorySaved: 'memory saved',
snapshotMemorySaved: 'Memory saved',
snapshotCreateButton: 'New snapshot',
tipCreateSnapshotLabel: 'Just click on the snapshot button to create one!',
revertSnapshot: 'Revert VM to this snapshot',
@@ -1098,7 +1057,6 @@ const messages = {
snapshotDate: 'Creation date',
snapshotName: 'Name',
snapshotDescription: 'Description',
snapshotAction: 'Action',
snapshotQuiesce: 'Quiesced snapshot',
vmRevertSuccessfulTitle: 'Revert successful',
vmRevertSuccessfulMessage: 'VM successfully reverted',
@@ -1159,10 +1117,8 @@ const messages = {
vmCoresPerSocketIncorrectValue: 'Incorrect cores per socket value',
vmCoresPerSocketIncorrectValueSolution:
'Please change the selected value to fix it.',
vmHaDisabled: 'disabled',
vmHaDisabled: 'Disabled',
vmMemoryLimitsLabel: 'Memory limits (min/max)',
vmMaxVcpus: 'vCPUs max:',
vmMaxRam: 'Memory max:',
vmVgpu: 'vGPU',
vmVgpus: 'GPUs',
vmVgpuNone: 'None',
@@ -1179,8 +1135,6 @@ const messages = {
vmHomeNamePlaceholder: 'Long click to add a name',
vmHomeDescriptionPlaceholder: 'Long click to add a description',
vmViewNamePlaceholder: 'Click to add a name',
vmViewDescriptionPlaceholder: 'Click to add a description',
// ----- Templates -----
@@ -1258,9 +1212,6 @@ const messages = {
statsDashboardSelectObjects: 'Select',
metricsLoading: 'Loading…',
// ----- Visualizations -----
comingSoon: 'Coming soon!',
// ----- Health -----
orphanedVdis: 'Orphaned snapshot VDIs',
orphanedVms: 'Orphaned VMs snapshot',
@@ -1281,7 +1232,6 @@ const messages = {
alarmContent: 'Content',
alarmObject: 'Issue on',
alarmPool: 'Pool',
alarmRemoveAll: 'Remove all alarms',
spaceLeftTooltip: '{used}% used ({free} left)',
// ----- New VM -----
@@ -1339,7 +1289,6 @@ const messages = {
newVmCreateVms: 'Create VMs',
newVmCreateVmsConfirm: 'Are you sure you want to create {nbVms, number} VMs?',
newVmMultipleVms: 'Multiple VMs:',
newVmSelectResourceSet: 'Select a resource set:',
newVmMultipleVmsPattern: 'Name pattern:',
newVmMultipleVmsPatternPlaceholder: 'e.g.: \\{name\\}_%',
newVmFirstIndex: 'First index:',
@@ -1379,9 +1328,6 @@ const messages = {
deleteResourceSetQuestion:
'Are you sure you want to delete this resource set?',
resourceSetMissingObjects: 'Missing objects:',
resourceSetVcpus: 'vCPUs',
resourceSetMemory: 'Memory',
resourceSetStorage: 'Storage',
unknownResourceSetValue: 'Unknown',
availableHosts: 'Available hosts',
excludedHosts: 'Excluded hosts',
@@ -1393,8 +1339,6 @@ const messages = {
maxDiskSpace: 'Maximum disk space',
ipPool: 'IP pool',
quantity: 'Quantity',
noResourceSetLimits: 'No limits.',
remainingResource: 'Remaining:',
usedResourceLabel: 'Used',
availableResourceLabel: 'Available',
resourceSetQuota: 'Used: {usage} (Total: {total})',
@@ -1457,7 +1401,6 @@ const messages = {
listRemote: 'List Remote',
simpleBackup: 'simple',
delta: 'delta',
restoreBackups: 'Restore Backups',
noBackups: 'There are no backups!',
restoreBackupsInfo: 'Click on a VM to display restore options',
restoreDeltaBackupsInfo:
@@ -1466,7 +1409,6 @@ const messages = {
remoteDisabled: 'Disabled',
enableRemote: 'Enable',
disableRemote: 'Disable',
remoteError: 'Error',
remoteErrorMessage:
'The URL ({url}) is invalid (colon in path). Click this button to change the URL to {newUrl}.',
backupVmNameColumn: 'VM Name',
@@ -1477,11 +1419,6 @@ const messages = {
availableBackupsColumn: 'Available Backups',
backupRestoreErrorTitle: 'Missing parameters',
backupRestoreErrorMessage: 'Choose a SR and a backup',
backupRestoreSelectDefaultSr: 'Select default SR…',
backupRestoreChooseSrForEachVdis: 'Choose a SR for each VDI',
backupRestoreVdiLabel: 'VDI',
backupRestoreSrLabel: 'SR',
displayBackup: 'Display backups',
importBackupTitle: 'Import VM',
importBackupMessage: 'Starting your backup import',
vmsToBackup: 'VMs to backup',
@@ -1515,7 +1452,6 @@ const messages = {
'Are you sure you want to delete all the backups from {nVms, number} VM{nVms, plural, one {} other {s}}?',
deleteVmBackupsBulkConfirmText:
'delete {nBackups} backup{nBackups, plural, one {} other {s}}',
unknownJob: 'Unknown job',
bulkDeleteMetadataBackupsTitle: 'Delete metadata backups',
bulkDeleteMetadataBackupsMessage:
'Are you sure you want to delete all the backups from {nMetadataBackups, number} metadata backup{nMetadataBackups, plural, one {} other {s}}?',
@@ -1530,9 +1466,7 @@ const messages = {
restoreFilesSelectBackup: 'Select a backup…',
restoreFilesSelectDisk: 'Select a disk…',
restoreFilesSelectPartition: 'Select a partition…',
restoreFilesSelectFolderPath: 'Folder path',
restoreFilesSelectFiles: 'Select a file…',
restoreFileContentNotFound: 'Content not found',
restoreFilesNoFilesSelected: 'No files selected',
restoreFilesSelectedFiles: 'Selected files ({files}):',
restoreFilesSelectedFilesAndFolders: 'Selected files/folders ({files}):',
@@ -1671,12 +1605,10 @@ const messages = {
deleteOrphanedVdisModalTitle: 'Delete orphaned snapshot VDIs',
deleteOrphanedVdisModalMessage:
'Are you sure you want to delete {nVdis, number} orphaned snapshot VDI{nVdis, plural, one {} other {s}}?',
removeAllLogsModalTitle: 'Remove all logs',
removeAllLogsModalWarning: 'Are you sure you want to remove all logs?',
definitiveMessageModal: 'This operation is definitive.',
existingLunModalTitle: 'Previous LUN Usage',
existingLunModalText:
'This LUN has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation.',
'This LUN has been previously used as storage by a XenServer host. All data will be lost if you choose to continue with the SR creation.',
alreadyRegisteredModal: 'Replace current registration?',
alreadyRegisteredModalText:
'Your XO appliance is already registered to {email}, do you want to forget and replace this registration ?',
@@ -1707,7 +1639,6 @@ const messages = {
serverHost: 'Host',
serverUsername: 'Username',
serverPassword: 'Password',
serverAction: 'Action',
serverReadOnly: 'Read Only',
serverUnauthorizedCertificates: 'Unauthorized Certificates',
serverAllowUnauthorizedCertificates: 'Allow Unauthorized Certificates',
@@ -1723,7 +1654,6 @@ const messages = {
serverAddFailed: 'Adding server failed',
serverStatus: 'Status',
serverConnectionFailed: 'Connection failed. Click for more information.',
serverConnecting: 'Connecting…',
serverConnected: 'Connected',
serverDisconnected: 'Disconnected',
serverAuthFailed: 'Authentication error',
@@ -1735,7 +1665,6 @@ const messages = {
// ----- Copy VM -----
copyVm: 'Copy VM',
copyVmName: 'Name',
copyVmNamePattern: 'Name pattern',
copyVmNamePlaceholder: 'If empty: name of the copied VM',
copyVmNamePatternPlaceholder: 'e.g.: "\\{name\\}_COPY"',
copyVmSelectSr: 'Select SR',
@@ -1786,12 +1715,11 @@ const messages = {
addHostNoHostMessage: 'No host selected to be added',
// ----- About View -----
xenOrchestra: 'Xen Orchestra',
xenOrchestraServer: 'Xen Orchestra server',
xenOrchestraWeb: 'Xen Orchestra web client',
noProSupport: 'No pro support provided!',
noProductionUse: 'Use in production at your own risks',
downloadXoaFromWebsite: 'You can download our turnkey appliance at {website}',
noProSupport: 'Professional support missing!',
noProductionUse: 'Use in production at your own risk',
downloadXoaFromWebsite: 'You can download the turnkey appliance at {website}',
bugTracker: 'Bug Tracker',
bugTrackerText: 'Issues? Report it!',
community: 'Community',
@@ -1817,7 +1745,6 @@ const messages = {
'This feature is not available in your version, contact your administrator to know more.',
// ----- Updates View -----
updateTitle: 'Updates',
registration: 'Registration',
settings: 'Settings',
proxySettings: 'Proxy settings',
@@ -1861,6 +1788,11 @@ const messages = {
upgradeWarningTitle: 'Upgrade warning',
upgradeWarningMessage:
'You have some backup jobs in progress. If you upgrade now, these jobs will be interrupted! Are you sure you want to continue?',
releaseChannels: 'Release channels',
unlistedChannel: 'unlisted channel',
unlistedChannelName: 'Unlisted channel name',
selectChannel: 'Select channel',
changeChannel: 'Change channel',
// ----- OS Disclaimer -----
disclaimerTitle: 'Xen Orchestra from the sources',
@@ -1907,11 +1839,15 @@ const messages = {
pwdChangeErrorBody:
'The old password provided is incorrect. Your password has not been changed.',
changePasswordOk: 'OK',
forgetTokens: 'Forget all connection tokens',
forgetTokensExplained:
'This will prevent other clients from authenticating with existing tokens but will not kill active sessions',
forgetTokensSuccess: 'Successfully forgot connection tokens',
forgetTokensError: 'Error while forgetting connection tokens',
sshKeys: 'SSH keys',
newSshKey: 'New SSH key',
deleteSshKey: 'Delete',
deleteSshKeys: 'Delete selected SSH keys',
noSshKeys: 'No SSH keys',
newSshKeyModalTitle: 'New SSH key',
sshKeyErrorTitle: 'Invalid key',
sshKeyErrorMessage: 'An SSH key requires both a title and a key.',
@@ -1934,22 +1870,16 @@ const messages = {
others: 'Others',
// ----- Logs -----
loadingLogs: 'Loading logs…',
logUser: 'User',
logMethod: 'Method',
logParams: 'Params',
logMessage: 'Message',
logSuggestXcpNg: 'Use XCP-ng to get rid of restrictions',
logError: 'Error',
logTitle: 'Logs',
logDisplayDetails: 'Display details',
logDownload: 'Download log',
logTime: 'Date',
logNoStackTrace: 'No stack trace',
logNoParams: 'No params',
logDelete: 'Delete log',
logsDelete: 'Delete logs',
logsThreePerPage: '3 / page',
logsTenPerPage: '10 / page',
logsJobId: 'Job ID',
logsJobName: 'Job name',
logsBackupTime: 'Backup time',
@@ -1973,12 +1903,9 @@ const messages = {
// ----- IPs ------
ipPoolName: 'Name',
ipPoolIps: 'IPs',
ipPoolIpsPlaceholder: 'IPs (e.g.: 1.0.0.12-1.0.0.17;1.0.0.23)',
ipPoolNetworks: 'Networks',
ipsNoIpPool: 'No IP pools',
ipsCreate: 'Create',
ipsDeleteAllTitle: 'Delete all IP pools',
ipsDeleteAllMessage: 'Are you sure you want to delete all the IP pools?',
ipsVifs: 'VIFs',
ipsNotUsed: 'Not used',
ipPoolUnknownVif: 'unknown VIF',
@@ -2027,7 +1954,7 @@ const messages = {
// ----- Config -----
noConfigFile: 'No config file selected',
importTip:
'Try dropping a config file here, or click to select a config file to upload.',
'Try dropping a config file here or click to select a config file to upload.',
config: 'Config',
importConfig: 'Import',
importConfigSuccess: 'Config file successfully imported',
@@ -2039,8 +1966,6 @@ const messages = {
// ----- SR -----
srReconnectAllModalTitle: 'Reconnect all hosts',
srReconnectAllModalMessage: 'This will reconnect this SR to all its hosts.',
srsReconnectAllModalMessage:
'This will reconnect each selected SR to its host (local SR) or to every hosts of its pool (shared SR).',
srDisconnectAllModalTitle: 'Disconnect all hosts',
srDisconnectAllModalMessage:
'This will disconnect this SR from all its hosts.',
@@ -2058,8 +1983,6 @@ const messages = {
// ----- XOSAN -----
xosanTitle: 'XOSAN',
xosanSrTitle: 'Xen Orchestra SAN SR',
xosanAvailableSrsTitle: 'Select local SRs (lvm)',
xosanSuggestions: 'Suggestions',
xosanDisperseWarning:
'Warning: using disperse layout is not recommended right now. Please read {link}.',
@@ -2067,7 +1990,6 @@ const messages = {
xosanHost: 'Host',
xosanHosts: 'Connected Hosts',
xosanPool: 'Pool',
xosanVolumeId: 'Volume ID',
xosanSize: 'Size',
xosanUsedSpace: 'Used space',
xosanLicense: 'License',
@@ -2078,8 +2000,6 @@ const messages = {
xosanNeedRestart:
'Some hosts need their toolstack to be restarted before you can create an XOSAN',
xosanRestartAgents: 'Restart toolstacks',
xosanMasterOffline: 'Pool master is not running',
xosanInstallPackTitle: 'Install XOSAN pack on {pool}',
xosanSrOnSameHostMessage: 'Select no more than 1 SR per host',
xosanLayout: 'Layout',
xosanRedundancy: 'Redundancy',
@@ -2087,8 +2007,6 @@ const messages = {
xosanAvailableSpace: 'Available space',
xosanDiskLossLegend: '* Can fail without data loss',
xosanCreate: 'Create',
xosanAdd: 'Add',
xosanInstalling: 'Installing XOSAN. Please wait…',
xosanCommunity: 'No XOSAN available for Community Edition',
xosanNew: 'New',
xosanAdvanced: 'Advanced',
@@ -2104,7 +2022,7 @@ const messages = {
xosanUpdatePacks: 'Update packs',
xosanPackUpdateChecking: 'Checking for updates',
xosanPackUpdateError:
'Error while checking XOSAN packs. Please make sure that the Cloud plugin is installed and loaded and that the updater is reachable.',
'Error while checking XOSAN packs. Please make sure that the Cloud plugin is installed and loaded, and that the updater is reachable.',
xosanPackUpdateUnavailable: 'XOSAN resources are unavailable',
xosanPackUpdateUnregistered: 'Not registered for XOSAN resources',
xosanPackUpdateUpToDate: "✓ This pool's XOSAN packs are up to date!",
@@ -2122,9 +2040,6 @@ const messages = {
// Pack download modal
xosanInstallCloudPlugin: 'Install cloud plugin first',
xosanLoadCloudPlugin: 'Load cloud plugin first',
xosanRegister: 'Register your appliance first',
xosanLoading: 'Loading…',
xosanNotAvailable: 'XOSAN is not available at the moment',
xosanNoPackFound:
'No compatible XOSAN pack found for your XenServer versions.',
// SR tab XOSAN
@@ -2169,7 +2084,6 @@ const messages = {
'Will configure the host xosan network device with a static IP address and plug it in.',
// Licenses
licensesTitle: 'Licenses',
xosanUnregisteredDisclaimer:
'You are not registered and therefore will not be able to create or manage your XOSAN SRs. {link}',
xosanSourcesDisclaimer:

View File

@@ -1,7 +1,9 @@
import forEachRight from 'lodash/forEachRight'
import forEach from 'lodash/forEach'
import forEachRight from 'lodash/forEachRight'
import head from 'lodash/head'
import isArray from 'lodash/isArray'
import isIp from 'is-ip'
import last from 'lodash/last'
import some from 'lodash/some'
export { isIp }
@@ -82,6 +84,9 @@ export const formatIps = ips => {
if (ips.length === 0) {
return []
}
if (ips.length === 1) {
return ips
}
const sortedIps = ips.sort((ip1, ip2) => {
const splitIp1 = ip1.split('.')
const splitIp2 = ip2.split('.')
@@ -99,24 +104,8 @@ export const formatIps = ips => {
(splitIp1[0] - splitIp2[0]) * 256 * 256 * 256
)
})
const range = { first: '', last: '' }
const formattedIps = []
let index = 0
forEach(sortedIps, ip => {
if (ip !== getNextIpV4(range.last)) {
if (range.first) {
formattedIps[index] =
range.first === range.last ? range.first : { ...range }
index++
}
range.first = range.last = ip
} else {
range.last = ip
}
})
formattedIps[index] = range.first === range.last ? range.first : range
return formattedIps
return [{ first: head(sortedIps), last: last(sortedIps) }]
}
export const parseIpPattern = pattern => {

View File

@@ -283,7 +283,7 @@ export const Vdi = decorate([
sr: getSr(state, props),
})
}),
({ id, sr, vdi }) => {
({ id, showSize, showSr, sr, vdi }) => {
if (vdi === undefined) {
return unknowItem(id, 'VDI')
}
@@ -291,9 +291,12 @@ export const Vdi = decorate([
return (
<span>
<Icon icon='disk' /> {vdi.name_label}
{sr !== undefined && (
{sr !== undefined && showSr && (
<span className='text-muted'> - {sr.name_label}</span>
)}
{showSize && (
<span className='text-muted'> ({formatSize(vdi.size)})</span>
)}
</span>
)
},
@@ -302,10 +305,13 @@ export const Vdi = decorate([
Vdi.propTypes = {
id: PropTypes.string.isRequired,
self: PropTypes.bool,
showSize: PropTypes.bool,
}
Vdi.defaultProps = {
self: false,
showSize: false,
showSr: false,
}
// ===================================================================
@@ -432,8 +438,8 @@ const xoItemToRender = {
// XO objects.
pool: ({ id }) => <Pool id={id} />,
VDI: ({ id }) => <Vdi id={id} />,
'VDI-resourceSet': ({ id }) => <Vdi id={id} self />,
VDI: ({ id }) => <Vdi id={id} showSr />,
'VDI-resourceSet': ({ id }) => <Vdi id={id} self showSr />,
// Pool objects.
'VM-template': ({ id }) => <VmTemplate id={id} />,

View File

@@ -59,7 +59,7 @@ export const constructSmartPattern = (
const valueToComplexMatcher = pattern => {
if (typeof pattern === 'string') {
return new CM.RegExpNode(`^${escapeRegExp(pattern)}$`)
return new CM.RegExpNode(`^${escapeRegExp(pattern)}$`, 'i')
}
if (Array.isArray(pattern)) {

View File

@@ -29,7 +29,7 @@ import _ from './intl'
import * as actions from './store/actions'
import invoke from './invoke'
import store from './store'
import { getObject } from './selectors'
import { getObject, isAdmin } from './selectors'
import { satisfies as versionSatisfies } from 'semver'
export const EMPTY_ARRAY = Object.freeze([])
@@ -650,3 +650,12 @@ export const hasLicenseRestrictions = host =>
host.productBrand !== 'XCP-ng' &&
versionSatisfies(host.version, '>=7.3.0') &&
host.license_params.sku_type === 'free'
// ===================================================================
export const adminOnly = Component =>
connectStore({
_isAdmin: isAdmin,
})(({ _isAdmin, ...props }) =>
_isAdmin ? <Component {...props} /> : <_NotFound />
)

View File

@@ -15,6 +15,7 @@ import {
max,
round,
size,
sortBy,
sum,
values,
} from 'lodash'
@@ -107,7 +108,7 @@ const makeLabelInterpolationFnc = (intl, nValues, endTimestamp, interval) => {
// Supported series: xvds, vifs, pifs.
const buildSeries = ({ stats, label, addSumSeries }) => {
const series = []
let series = []
for (const io in stats) {
const ioData = stats[io]
@@ -123,6 +124,8 @@ const buildSeries = ({ stats, label, addSumSeries }) => {
}
}
series = sortBy(series, 'name')
if (addSumSeries) {
series.push({
name: `All ${io}`,
@@ -588,10 +591,13 @@ PoolLoadLineChart.propTypes = {
}
const buildSrSeries = ({ stats, label, addSumSeries }) => {
const series = map(stats, (data, key) => ({
name: `${label} (${key})`,
data,
}))
const series = sortBy(
map(stats, (data, key) => ({
name: `${label} (${key})`,
data,
})),
'name'
)
if (addSumSeries) {
series.push({

View File

@@ -3,6 +3,7 @@ import Component from 'base-component'
import PropTypes from 'prop-types'
import React from 'react'
import { map } from 'lodash'
import { Vdi } from 'render-xo-item'
import _ from '../../intl'
import SingleLineRow from '../../single-line-row'
@@ -85,7 +86,9 @@ export default class ChooseSrForEachVdisModal extends Component {
</SingleLineRow>
{map(props.vdis, vdi => (
<SingleLineRow key={vdi.uuid}>
<Col size={6}>{vdi.name_label || vdi.name}</Col>
<Col size={6}>
<Vdi id={vdi.id} showSize />
</Col>
<Col size={6}>
<SelectSr
onChange={sr =>

View File

@@ -376,7 +376,7 @@ export const dismissNotification = id => {
export const subscribeNotifications = createSubscription(async () => {
const { user, xoaUpdaterState } = store.getState()
if (
process.env.XOA_PLAN === 5 ||
+process.env.XOA_PLAN === 5 ||
xoaUpdaterState === 'disconnected' ||
xoaUpdaterState === 'error'
) {
@@ -2508,14 +2508,25 @@ export const editUser = (user, { email, password, permission }) =>
subscribeUsers.forceRefresh
)
const _signOutFromEverywhereElse = () =>
_call('token.deleteAll', { except: cookies.get('token') })
export const signOutFromEverywhereElse = () =>
_signOutFromEverywhereElse().then(
() => success(_('forgetTokens'), _('forgetTokensSuccess')),
() => error(_('forgetTokens'), _('forgetTokensError'))
)
export const changePassword = (oldPassword, newPassword) =>
_call('user.changePassword', {
oldPassword,
newPassword,
}).then(
() => success(_('pwdChangeSuccess'), _('pwdChangeSuccessBody')),
() => error(_('pwdChangeError'), _('pwdChangeErrorBody'))
)
})
.then(_signOutFromEverywhereElse)
.then(
() => success(_('pwdChangeSuccess'), _('pwdChangeSuccessBody')),
() => error(_('pwdChangeError'), _('pwdChangeErrorBody'))
)
const _setUserPreferences = preferences =>
_call('user.set', {

View File

@@ -381,6 +381,13 @@ class XoaUpdater extends EventEmitter {
}
}
getReleaseChannels() {
return this._call('getReleaseChannels').catch(error => {
console.error('getReleaseChannels', error)
return {}
})
}
async _call(...args) {
const c = await this._open()
try {

View File

@@ -7,6 +7,7 @@ const DEFAULTS = {
concurrency: 0,
fullInterval: 0,
offlineSnapshot: false,
reportWhen: 'failure',
timeout: 0,
}

View File

@@ -12,9 +12,9 @@ import React from 'react'
import SortedTable from 'sorted-table'
import StateButton from 'state-button'
import Tooltip from 'tooltip'
import { adminOnly, connectStore, routes } from 'utils'
import { Card, CardHeader, CardBlock } from 'card'
import { confirm } from 'modal'
import { connectStore, routes } from 'utils'
import { constructQueryString } from 'smart-backup'
import { Container, Row, Col } from 'grid'
import { createGetLoneSnapshots, createSelector } from 'selectors'
@@ -430,8 +430,10 @@ export default routes('overview', {
'restore/metadata': RestoreMetadata,
'file-restore': FileRestore,
health: Health,
})(({ children }) => (
<Page header={HEADER} title='backupPage' formatTitle>
{children}
</Page>
))
})(
adminOnly(({ children }) => (
<Page header={HEADER} title='backupPage' formatTitle>
{children}
</Page>
))
)

View File

@@ -0,0 +1,70 @@
import _ from 'intl'
import decorate from 'apply-decorators'
import Icon from 'icon'
import Link from 'link'
import PropTypes from 'prop-types'
import React from 'react'
import Select from 'form/select'
import Tooltip from 'tooltip'
import { generateId } from 'reaclette-utils'
import { injectState, provideState } from 'reaclette'
import { FormGroup } from './../utils'
const REPORT_WHEN_FILTER_OPTIONS = [
{
label: 'reportWhenAlways',
value: 'always',
},
{
label: 'reportWhenFailure',
value: 'failure',
},
{
label: 'reportWhenNever',
value: 'never',
},
]
const getOptionRenderer = ({ label }) => <span>{_(label)}</span>
const ReportWhen = decorate([
provideState({
computed: {
idInput: generateId,
},
}),
injectState,
({ state, onChange, value, ...props }) => (
<FormGroup>
<label htmlFor={state.idInput}>
<strong>{_('reportWhen')}</strong>
</label>{' '}
<Tooltip content={_('pluginsWarning')}>
<Link
className='btn btn-primary btn-sm'
target='_blank'
to='/settings/plugins'
>
<Icon icon='menu-settings-plugins' />{' '}
<strong>{_('pluginsSettings')}</strong>
</Link>
</Tooltip>
<Select
id={state.idInput}
onChange={onChange}
optionRenderer={getOptionRenderer}
options={REPORT_WHEN_FILTER_OPTIONS}
value={value}
{...props}
/>
</FormGroup>
),
])
ReportWhen.propTypes = {
onChange: PropTypes.func.isRequired,
value: PropTypes.string,
}
export { ReportWhen as default }

View File

@@ -7,7 +7,6 @@ import Icon from 'icon'
import Link from 'link'
import moment from 'moment-timezone'
import React from 'react'
import Select from 'form/select'
import Tooltip from 'tooltip'
import Upgrade from 'xoa-upgrade'
import UserError from 'user-error'
@@ -42,6 +41,7 @@ import {
} from 'xo'
import NewSchedule from './new-schedule'
import ReportWhen from './_reportWhen'
import Schedules from './schedules'
import SmartBackup from './smart-backup'
import getSettingsWithNonDefaultValue from '../_getSettingsWithNonDefaultValue'
@@ -115,23 +115,6 @@ const destructVmsPattern = pattern =>
vms: destructPattern(pattern),
}
const REPORT_WHEN_FILTER_OPTIONS = [
{
label: 'reportWhenAlways',
value: 'always',
},
{
label: 'reportWhenFailure',
value: 'failure',
},
{
label: 'reportWhenNever',
value: 'never',
},
]
const getOptionRenderer = ({ label }) => <span>{_(label)}</span>
const createDoesRetentionExist = name => {
const predicate = setting => setting[name] > 0
return ({ propSettings, settings = propSettings }) => settings.some(predicate)
@@ -541,7 +524,6 @@ export default decorate([
formId: generateId,
inputConcurrencyId: generateId,
inputFullIntervalId: generateId,
inputReportWhenId: generateId,
inputTimeoutId: generateId,
vmsPattern: ({ _vmsPattern }, { job }) =>
@@ -884,31 +866,13 @@ export default decorate([
</ActionButton>
</CardHeader>
<CardBlock>
<FormGroup>
<label htmlFor={state.inputReportWhenId}>
<strong>{_('reportWhen')}</strong>
</label>{' '}
<Tooltip content={_('pluginsWarning')}>
<Link
className='btn btn-primary btn-sm'
target='_blank'
to='/settings/plugins'
>
<Icon icon='menu-settings-plugins' />{' '}
<strong>{_('pluginsSettings')}</strong>
</Link>
</Tooltip>
<Select
id={state.inputReportWhenId}
labelKey='label'
onChange={effects.setReportWhen}
optionRenderer={getOptionRenderer}
options={REPORT_WHEN_FILTER_OPTIONS}
required
value={reportWhen}
valueKey='value'
/>
</FormGroup>
<ReportWhen
onChange={effects.setReportWhen}
required
// Handle improper value introduced by:
// https://github.com/vatesfr/xen-orchestra/commit/753ee994f2948bbaca9d3161eaab82329a682773#diff-9c044ab8a42ed6576ea927a64c1ec3ebR105
value={reportWhen === 'Never' ? 'never' : reportWhen}
/>
{state.displayAdvancedSettings && (
<div>
<FormGroup>

View File

@@ -33,6 +33,7 @@ import {
Ul,
} from '../../utils'
import ReportWhen from '../_reportWhen'
import Schedules from '../_schedules'
// A retention can be:
@@ -51,6 +52,25 @@ const RETENTION_XO_METADATA = {
valuePath: 'retentionXoMetadata',
}
const GLOBAL_SETTING_KEY = ''
const setSettingsDefaultRetentions = (
settings,
{ modePoolMetadata, modeXoMetadata }
) =>
mapValues(settings, (setting, key) =>
key !== GLOBAL_SETTING_KEY
? {
retentionPoolMetadata: modePoolMetadata
? defined(setting.retentionPoolMetadata, DEFAULT_RETENTION)
: undefined,
retentionXoMetadata: modeXoMetadata
? defined(setting.retentionXoMetadata, DEFAULT_RETENTION)
: undefined,
}
: setting
)
const getInitialState = () => ({
_modePoolMetadata: undefined,
_modeXoMetadata: undefined,
@@ -79,28 +99,25 @@ export default decorate([
return { showErrors: true }
}
const {
modePoolMetadata,
modeXoMetadata,
name,
pools,
remotes,
schedules,
settings,
} = state
await createMetadataBackupJob({
name: state.name,
pools: state.modePoolMetadata
? constructPattern(state.pools)
: undefined,
remotes: constructPattern(state.remotes),
xoMetadata: state.modeXoMetadata,
schedules: mapValues(
state.schedules,
({ id, ...schedule }) => schedule
),
settings: mapValues(
state.settings,
({ retentionPoolMetadata, retentionXoMetadata }) => ({
retentionPoolMetadata: state.modePoolMetadata
? defined(retentionPoolMetadata, DEFAULT_RETENTION)
: undefined,
retentionXoMetadata: state.modeXoMetadata
? defined(retentionXoMetadata, DEFAULT_RETENTION)
: undefined,
})
),
name,
pools: modePoolMetadata ? constructPattern(pools) : undefined,
remotes: constructPattern(remotes),
schedules: mapValues(schedules, ({ id, ...schedule }) => schedule),
settings: setSettingsDefaultRetentions(settings, {
modePoolMetadata,
modeXoMetadata,
}),
xoMetadata: modeXoMetadata,
})
},
editJob: () => async (state, props) => {
@@ -138,23 +155,17 @@ export default decorate([
}),
])
const { modePoolMetadata, modeXoMetadata, name, pools, remotes } = state
await editMetadataBackupJob({
id: props.job.id,
name: state.name,
pools: state.modePoolMetadata ? constructPattern(state.pools) : null,
remotes: constructPattern(state.remotes),
xoMetadata: state.modeXoMetadata,
settings: mapValues(
settings,
({ retentionPoolMetadata, retentionXoMetadata }) => ({
retentionPoolMetadata: state.modePoolMetadata
? defined(retentionPoolMetadata, DEFAULT_RETENTION)
: undefined,
retentionXoMetadata: state.modeXoMetadata
? defined(retentionXoMetadata, DEFAULT_RETENTION)
: undefined,
})
),
name,
pools: modePoolMetadata ? constructPattern(pools) : null,
remotes: constructPattern(remotes),
settings: setSettingsDefaultRetentions(settings, {
modePoolMetadata,
modeXoMetadata,
}),
xoMetadata: modeXoMetadata,
})
},
@@ -169,6 +180,20 @@ export default decorate([
setSettings: (_, _settings) => () => ({
_settings,
}),
setGlobalSettings: ({ setSettings }, name, value) => ({
settings = {},
}) => {
setSettings({
...settings,
[GLOBAL_SETTING_KEY]: {
...settings[GLOBAL_SETTING_KEY],
[name]: value,
},
})
},
setReportWhen({ setGlobalSettings }, { value }) {
setGlobalSettings('reportWhen', value)
},
toggleMode: (_, { mode }) => state => ({
[`_${mode}`]: !state[mode],
}),
@@ -265,6 +290,11 @@ export default decorate([
missingSchedules,
} = state.showErrors ? state : {}
const { reportWhen = 'failure' } = defined(
() => state.settings[GLOBAL_SETTING_KEY],
{}
)
return (
<form id={state.idForm}>
<Container>
@@ -357,6 +387,16 @@ export default decorate([
)}
</CardBlock>
</Card>
<Card>
<CardHeader>{_('newBackupSettings')}</CardHeader>
<CardBlock>
<ReportWhen
onChange={effects.setReportWhen}
required
value={reportWhen}
/>
</CardBlock>
</Card>
</Col>
<Col mediumSize={6}>
{state.modePoolMetadata && (

View File

@@ -3,7 +3,7 @@ import Icon from 'icon'
import Link from 'link'
import Page from '../page'
import React from 'react'
import { routes } from 'utils'
import { adminOnly, routes } from 'utils'
import { Container, Row, Col } from 'grid'
import { NavLink, NavTabs } from 'nav'
@@ -68,10 +68,12 @@ const Backup = routes('overview', {
overview: Overview,
restore: MovingRestoreMessage,
'file-restore': MovingFileRestoreMessage,
})(({ children }) => (
<Page header={HEADER} title='backupPage' formatTitle>
{children}
</Page>
))
})(
adminOnly(({ children }) => (
<Page header={HEADER} title='backupPage' formatTitle>
{children}
</Page>
))
)
export default Backup

View File

@@ -750,7 +750,8 @@ export default class Home extends Component {
new ComplexMatcher.Or(
map(
tags,
tag => new ComplexMatcher.RegExp(`^${escapeRegExp(tag.id)}$`)
tag =>
new ComplexMatcher.RegExp(`^${escapeRegExp(tag.id)}$`, 'i')
)
)
)

View File

@@ -118,7 +118,9 @@ export default ({ statsOverview, host, nVms, vmController, vms }) => {
<Usage total={host.memory.size}>
<UsageElement
highlight
tooltip={`XenServer (${formatSize(vmController.memory.size)})`}
tooltip={`${host.productBrand} (${formatSize(
vmController.memory.size
)})`}
value={vmController.memory.size}
/>
{map(vms, vm => (

View File

@@ -4,7 +4,7 @@ import Page from '../page'
import React from 'react'
import { Container, Row, Col } from 'grid'
import { NavLink, NavTabs } from 'nav'
import { routes } from 'utils'
import { adminOnly, routes } from 'utils'
import Edit from './edit'
import New from './new'
@@ -43,10 +43,12 @@ const Jobs = routes('overview', {
overview: Overview,
schedules: Schedules,
'schedules/:id/edit': EditSchedule,
})(({ children }) => (
<Page header={HEADER} title='jobsPage' formatTitle>
{children}
</Page>
))
})(
adminOnly(({ children }) => (
<Page header={HEADER} title='jobsPage' formatTitle>
{children}
</Page>
))
)
export default Jobs

View File

@@ -15,7 +15,7 @@ import store from 'store'
import trim from 'lodash/trim'
import Wizard, { Section } from 'wizard'
import { confirm } from 'modal'
import { connectStore, formatSize } from 'utils'
import { adminOnly, connectStore, formatSize } from 'utils'
import { Container, Row, Col } from 'grid'
import { injectIntl } from 'react-intl'
import { Password, Select, Toggle } from 'form'
@@ -216,6 +216,7 @@ const getSrPath = id => (id !== undefined ? `/srs/${id}` : undefined)
// ===================================================================
@adminOnly
@injectIntl
@connectStore(() => ({
hosts: createGetObjectsOfType('host'),

View File

@@ -25,7 +25,12 @@ import Upgrade from 'xoa-upgrade'
import { Container, Row, Col } from 'grid'
import { injectIntl } from 'react-intl'
import { SizeInput } from 'form'
import { addSubscriptions, connectStore, resolveIds } from 'utils'
import {
addSubscriptions,
adminOnly,
connectStore,
resolveIds
} from 'utils'
import {
createGetObjectsOfType,
createSelector,
@@ -700,6 +705,7 @@ class ResourceSet extends Component {
const compareName = (a, b) => (a.name < b.name ? -1 : 1)
@adminOnly
@addSubscriptions({ resourceSets: subscribeResourceSets })
@connectStore({ resolvedResourceSets: getResolvedResourceSets })
export default class Self extends Component {

View File

@@ -2,7 +2,7 @@ import _ from 'intl'
import Icon from 'icon'
import Page from '../page'
import React from 'react'
import { routes } from 'utils'
import { adminOnly, routes } from 'utils'
import { Container, Row, Col } from 'grid'
import { NavLink, NavTabs } from 'nav'
@@ -74,10 +74,12 @@ const Settings = routes('servers', {
remotes: Remotes,
servers: Servers,
users: Users,
})(({ children }) => (
<Page header={HEADER} title='settingsPage' formatTitle>
{children}
</Page>
))
})(
adminOnly(({ children }) => (
<Page header={HEADER} title='settingsPage' formatTitle>
{children}
</Page>
))
)
export default Settings

View File

@@ -51,9 +51,20 @@ const COLUMNS = [
{
name: _('logMessage'),
itemRenderer: log => (
<pre className={styles.widthLimit}>
{log.data.error && log.data.error.message}
</pre>
<span>
<pre className={styles.widthLimit}>
{log.data.error && log.data.error.message}
</pre>
{log.data.error && log.data.error.code === 'LICENCE_RESTRICTION' && (
<a
href='https://xcp-ng.org/'
rel='noopener noreferrer'
target='_blank'
>
{_('logSuggestXcpNg')}
</a>
)}
</span>
),
sortCriteria: log => log.data.error && log.data.error.message,
},

View File

@@ -7,6 +7,7 @@ import Icon from 'icon'
import PropTypes from 'prop-types'
import React from 'react'
import SortedTable from 'sorted-table'
import Tooltip from 'tooltip'
import { Text } from 'editable'
import { alert } from 'modal'
import { Container, Row, Col } from 'grid'
@@ -24,6 +25,7 @@ import {
editCustomFilter,
removeCustomFilter,
setDefaultHomeFilter,
signOutFromEverywhereElse,
subscribeCurrentUser,
} from 'xo'
@@ -400,6 +402,20 @@ export default class User extends Component {
</Col>
</Row>
<br />
<Row>
<Col smallSize={10} offset={2}>
<Tooltip content={_('forgetTokensExplained')}>
<ActionButton
btnStyle='danger'
handler={signOutFromEverywhereElse}
icon='disconnect'
>
{_('forgetTokens')}
</ActionButton>
</Tooltip>
</Col>
</Row>
<br />
<Row>
<Col smallSize={2}>
<strong>{_('language')}</strong>

View File

@@ -59,160 +59,178 @@ export default connectStore(() => {
vgpuTypes,
vm,
vmTotalDiskSpace,
}) => (
<Container>
{/* TODO: use CSS style */}
<br />
<Row className='text-xs-center'>
<Col mediumSize={3}>
<h2>
<Number
value={vm.CPUs.number}
onChange={vcpus => editVm(vm, { CPUs: vcpus })}
/>
x <Icon icon='cpu' size='lg' />
</h2>
<BlockLink to={`/vms/${vm.id}/stats`}>
{statsOverview && <CpuSparkLines data={statsOverview} />}
</BlockLink>
</Col>
<Col mediumSize={3}>
<h2 className='form-inline'>
<Size
value={defined(vm.memory.dynamic[1], null)}
onChange={memory => editVm(vm, { memory })}
/>
&nbsp;
<span>
<Icon icon='memory' size='lg' />
</span>
</h2>
<BlockLink to={`/vms/${vm.id}/stats`}>
{statsOverview && <MemorySparkLines data={statsOverview} />}
</BlockLink>
</Col>
<Col mediumSize={3}>
<BlockLink to={`/vms/${vm.id}/network`}>
}) => {
const {
addresses,
CPUs: cpus,
current_operations: currentOperations,
id,
installTime,
memory,
os_version: osVersion,
power_state: powerState,
startTime,
tags,
VIFs: vifs,
xenTools,
} = vm
return (
<Container>
{/* TODO: use CSS style */}
<br />
<Row className='text-xs-center'>
<Col mediumSize={3}>
<h2>
{vm.VIFs.length}x <Icon icon='network' size='lg' />
<Number
value={cpus.number}
onChange={vcpus => editVm(vm, { CPUs: vcpus })}
/>
x <Icon icon='cpu' size='lg' />
</h2>
</BlockLink>
<BlockLink to={`/vms/${vm.id}/stats`}>
{statsOverview && <NetworkSparkLines data={statsOverview} />}
</BlockLink>
</Col>
<Col mediumSize={3}>
<BlockLink to={`/vms/${vm.id}/disks`}>
<h2>
{formatSize(vmTotalDiskSpace)} <Icon icon='disk' size='lg' />
<BlockLink to={`/vms/${id}/stats`}>
{statsOverview && <CpuSparkLines data={statsOverview} />}
</BlockLink>
</Col>
<Col mediumSize={3}>
<h2 className='form-inline'>
<Size
value={defined(memory.dynamic[1], null)}
onChange={memory => editVm(vm, { memory })}
/>
&nbsp;
<span>
<Icon icon='memory' size='lg' />
</span>
</h2>
</BlockLink>
<BlockLink to={`/vms/${vm.id}/stats`}>
{statsOverview && <XvdSparkLines data={statsOverview} />}
</BlockLink>
</Col>
</Row>
{/* TODO: use CSS style */}
<br />
<Row className='text-xs-center'>
<Col mediumSize={3}>
{vm.installTime !== null && (
<div className='text-xs-center'>
{_('created', {
date: (
<FormattedDate
day='2-digit'
month='long'
value={vm.installTime * 1000}
year='numeric'
/>
),
})}
</div>
)}
{vm.power_state === 'Running' ? (
<div>
<p className='text-xs-center'>
{_('started', {
ago: <FormattedRelative value={vm.startTime * 1000} />,
<BlockLink to={`/vms/${id}/stats`}>
{statsOverview && <MemorySparkLines data={statsOverview} />}
</BlockLink>
</Col>
<Col mediumSize={3}>
<BlockLink to={`/vms/${id}/network`}>
<h2>
{vifs.length}x <Icon icon='network' size='lg' />
</h2>
</BlockLink>
<BlockLink to={`/vms/${id}/stats`}>
{statsOverview && <NetworkSparkLines data={statsOverview} />}
</BlockLink>
</Col>
<Col mediumSize={3}>
<BlockLink to={`/vms/${id}/disks`}>
<h2>
{formatSize(vmTotalDiskSpace)} <Icon icon='disk' size='lg' />
</h2>
</BlockLink>
<BlockLink to={`/vms/${id}/stats`}>
{statsOverview && <XvdSparkLines data={statsOverview} />}
</BlockLink>
</Col>
</Row>
{/* TODO: use CSS style */}
<br />
<Row className='text-xs-center'>
<Col mediumSize={3}>
{installTime !== null && (
<div className='text-xs-center'>
{_('created', {
date: (
<FormattedDate
day='2-digit'
month='long'
value={installTime * 1000}
year='numeric'
/>
),
})}
</p>
</div>
) : (
<p className='text-xs-center'>
{lastShutdownTime
? _('vmHaltedSince', {
ago: <FormattedRelative value={lastShutdownTime * 1000} />,
})
: _('vmNotRunning')}
</p>
)}
</Col>
<Col mediumSize={3}>
<p>{_(getVirtualizationModeLabel(vm))}</p>
{vgpu !== undefined && (
<p>{renderXoItem(vgpuTypes[vgpu.vgpuType])}</p>
)}
</Col>
<Col mediumSize={3}>
<BlockLink to={`/vms/${vm.id}/network`}>
{vm.addresses && vm.addresses['0/ip'] ? (
<Copiable tagName='p'>{vm.addresses['0/ip']}</Copiable>
) : (
<p>{_('noIpv4Record')}</p>
</div>
)}
</BlockLink>
</Col>
<Col mediumSize={3}>
<BlockLink to={`/vms/${vm.id}/advanced`}>
<Tooltip
content={vm.os_version ? vm.os_version.name : _('unknownOsName')}
>
<h1>
<Icon
className='text-info'
icon={
vm.os_version &&
vm.os_version.distro &&
osFamily(vm.os_version.distro)
}
/>
</h1>
</Tooltip>
</BlockLink>
</Col>
</Row>
{!vm.xenTools && vm.power_state === 'Running' && (
<Row className='text-xs-center'>
<Col>
<Icon icon='error' />
<em> {_('noToolsDetected')}.</em>
{powerState === 'Running' || powerState === 'Paused' ? (
<div>
<p className='text-xs-center'>
{_('started', {
ago: <FormattedRelative value={startTime * 1000} />,
})}
</p>
</div>
) : (
<p className='text-xs-center'>
{lastShutdownTime
? _('vmHaltedSince', {
ago: (
<FormattedRelative value={lastShutdownTime * 1000} />
),
})
: _('vmNotRunning')}
</p>
)}
</Col>
<Col mediumSize={3}>
<p>{_(getVirtualizationModeLabel(vm))}</p>
{vgpu !== undefined && (
<p>{renderXoItem(vgpuTypes[vgpu.vgpuType])}</p>
)}
</Col>
<Col mediumSize={3}>
<BlockLink to={`/vms/${id}/network`}>
{addresses && addresses['0/ip'] ? (
<Copiable tagName='p'>{addresses['0/ip']}</Copiable>
) : (
<p>{_('noIpv4Record')}</p>
)}
</BlockLink>
</Col>
<Col mediumSize={3}>
<BlockLink to={`/vms/${id}/advanced`}>
<Tooltip
content={osVersion ? osVersion.name : _('unknownOsName')}
>
<h1>
<Icon
className='text-info'
icon={
osVersion &&
osVersion.distro &&
osFamily(osVersion.distro)
}
/>
</h1>
</Tooltip>
</BlockLink>
</Col>
</Row>
)}
{/* TODO: use CSS style */}
<br />
<Row>
<Col>
<h2 className='text-xs-center'>
<HomeTags
type='VM'
labels={vm.tags}
onDelete={tag => removeTag(vm.id, tag)}
onAdd={tag => addTag(vm.id, tag)}
/>
</h2>
</Col>
</Row>
{isEmpty(vm.current_operations) ? null : (
<Row className='text-xs-center'>
{!xenTools && powerState === 'Running' && (
<Row className='text-xs-center'>
<Col>
<Icon icon='error' />
<em> {_('noToolsDetected')}.</em>
</Col>
</Row>
)}
{/* TODO: use CSS style */}
<br />
<Row>
<Col>
<h4>
{_('vmCurrentStatus')} {map(vm.current_operations)[0]}
</h4>
<h2 className='text-xs-center'>
<HomeTags
type='VM'
labels={tags}
onDelete={tag => removeTag(id, tag)}
onAdd={tag => addTag(id, tag)}
/>
</h2>
</Col>
</Row>
)}
</Container>
)
{isEmpty(currentOperations) ? null : (
<Row className='text-xs-center'>
<Col>
<h4>
{_('vmCurrentStatus')} {map(currentOperations)[0]}
</h4>
</Col>
</Row>
)}
</Container>
)
}
)

View File

@@ -37,6 +37,8 @@ import {
map,
remove,
some,
uniq,
values,
} from 'lodash'
import {
@@ -515,6 +517,14 @@ export default class TabNetwork extends BaseComponent {
newVif: !this.state.newVif,
})
_getIpAddresses = createSelector(
() => this.props.vm.addresses,
// VM_guest_metrics.networks seems to always have 3 fields (ip, ipv4 and ipv6) for each interface
// http://xenbits.xenproject.org/docs/4.12-testing/misc/xenstore-paths.html#attrvifdevidipv4index-ipv4_address-w
// https://github.com/xapi-project/xen-api/blob/d650621ba7b64a82aeb77deca787acb059636eaf/ocaml/xapi/xapi_guest_agent.ml#L76-L79
addresses => uniq(values(addresses))
)
render() {
const { newVif } = this.state
const { pool, vm, vifs, networks } = this.props
@@ -551,8 +561,8 @@ export default class TabNetwork extends BaseComponent {
{!isEmpty(vm.addresses) ? (
<span>
<h4>{_('vifIpAddresses')}</h4>
{map(vm.addresses, (address, key) => (
<span key={key} className='tag tag-info tag-ip'>
{map(this._getIpAddresses(), address => (
<span key={address} className='tag tag-info tag-ip'>
{address}
</span>
))}

View File

@@ -6,7 +6,13 @@ import Link from 'link'
import React from 'react'
import renderXoItem from 'render-xo-item'
import SortedTable from 'sorted-table'
import { addSubscriptions, connectStore, getXoaPlan, ShortDate } from 'utils'
import {
addSubscriptions,
adminOnly,
connectStore,
getXoaPlan,
ShortDate,
} from 'utils'
import { Container, Row, Col } from 'grid'
import { createSelector, createGetObjectsOfType } from 'selectors'
import { find, forEach } from 'lodash'
@@ -77,6 +83,7 @@ const getBoundXosanRenderer = (boundObjectId, xosanSrs) => {
return () => <Link to={`srs/${sr.id}`}>{renderXoItem(sr)}</Link>
}
@adminOnly
@connectStore({
xosanSrs: createGetObjectsOfType('SR').filter([
({ SR_type }) => SR_type === 'xosan', // eslint-disable-line camelcase

View File

@@ -9,16 +9,17 @@ import Icon from 'icon'
import React from 'react'
import Tooltip from 'tooltip'
import xoaUpdater, { exposeTrial, isTrialRunning } from 'xoa-updater'
import { addSubscriptions, connectStore } from 'utils'
import { addSubscriptions, adminOnly, connectStore } from 'utils'
import { Card, CardBlock, CardHeader } from 'card'
import { confirm } from 'modal'
import { Container, Row, Col } from 'grid'
import { error } from 'notification'
import { generateId, linkState, toggleState } from 'reaclette-utils'
import { injectIntl } from 'react-intl'
import { injectState, provideState } from 'reaclette'
import { Input as DebounceInput } from 'debounce-input-decorator'
import { isEmpty, map, pick, some, zipObject } from 'lodash'
import { linkState, toggleState } from 'reaclette-utils'
import { Password } from 'form'
import { Password, Select } from 'form'
import { subscribeBackupNgJobs, subscribeJobs } from 'xo'
const ansiUp = new AnsiUp()
@@ -59,16 +60,22 @@ const LEVELS_TO_CLASSES = {
error: 'text-danger',
}
const UNLISTED_CHANNEL_VALUE = ''
const PROXY_ENTRIES = ['proxyHost', 'proxyPassword', 'proxyPort', 'proxyUser']
const initialProxyState = () => zipObject(PROXY_ENTRIES)
const REGISTRATION_ENTRIES = ['email', 'password']
const initialRegistrationState = () => zipObject(REGISTRATION_ENTRIES)
const CHANNEL_ENTRIES = ['channel']
const initialChannelState = () => zipObject(CHANNEL_ENTRIES)
const helper = (obj1, obj2, prop) =>
defined(() => obj1[prop], () => obj2[prop], '')
const Updates = decorate([
adminOnly,
addSubscriptions({
backupNgJobs: subscribeBackupNgJobs,
jobs: subscribeJobs,
@@ -82,6 +89,7 @@ const Updates = decorate([
]),
provideState({
initialState: () => ({
...initialChannelState(),
...initialProxyState(),
...initialRegistrationState(),
askRegisterAgain: false,
@@ -89,20 +97,28 @@ const Updates = decorate([
}),
effects: {
async configure() {
await xoaUpdater.configure(
pick(this.state, [
await xoaUpdater.configure({
...pick(this.state, [
'channel',
'proxyHost',
'proxyPassword',
'proxyPort',
'proxyUser',
])
)
return this.effects.resetProxyConfig()
]),
})
const { effects } = this
await Promise.all([
effects.resetChannel(),
effects.resetProxyConfig(),
effects.update(),
])
},
initialize() {
return this.effects.update()
},
linkState,
onChannelChange: (_, channel) => ({ channel }),
async register() {
const { state } = this
@@ -133,6 +149,7 @@ const Updates = decorate([
return initialRegistrationState()
},
resetChannel: initialChannelState,
resetProxyConfig: initialProxyState,
async startTrial() {
try {
@@ -152,13 +169,41 @@ const Updates = decorate([
},
toggleState,
update: () => xoaUpdater.update(),
upgrade: () => xoaUpdater.upgrade(),
upgrade() {
return this.state.areJobsRunning
? confirm({
title: _('upgradeWarningTitle'),
body: <p>{_('upgradeWarningMessage')}</p>,
}).then(() => xoaUpdater.upgrade())
: xoaUpdater.upgrade()
},
},
computed: {
areJobsRunning: (_, { jobs, backupNgJobs }) =>
jobs !== undefined &&
backupNgJobs !== undefined &&
some(jobs.concat(backupNgJobs), job => job.runId !== undefined),
channelsFormId: generateId,
channels: () => xoaUpdater.getReleaseChannels(),
channelsOptions: ({ channels }) =>
channels === undefined
? undefined
: [
...Object.keys(channels)
.sort()
.map(channel => ({
label: channel,
value: channel,
})),
{
label: (
<span className='font-italic'>{_('unlistedChannel')}</span>
),
value: UNLISTED_CHANNEL_VALUE,
},
],
consolidatedChannel: ({ channel }, { xoaConfiguration }) =>
defined(channel, xoaConfiguration.channel),
async installedPackages() {
const { installer, updater, npm } = await xoaUpdater.getLocalManifest()
return { ...installer, ...updater, ...npm }
@@ -178,6 +223,8 @@ const Updates = decorate([
xoaTrialState.state === 'default' &&
!isTrialRunning(xoaTrialState.trial) &&
!exposeTrial(xoaTrialState.trial),
isUnlistedChannel: ({ consolidatedChannel, channels }) =>
consolidatedChannel !== undefined && !(consolidatedChannel in channels),
isUpdaterDown: (_, { xoaTrialState }) =>
isEmpty(xoaTrialState) || xoaTrialState.state === 'ERROR',
packagesList: ({ installedPackages }) =>
@@ -202,7 +249,7 @@ const Updates = decorate([
}) => (
<Container>
<Row>
<Col mediumSize={12}>
<Col mediumSize={6}>
<Card>
<CardHeader>
<UpdateTag /> {LABELS_BY_STATE[xoaUpdaterState]}
@@ -245,7 +292,6 @@ const Updates = decorate([
</ActionButton>{' '}
<ActionButton
btnStyle='success'
data-runningJobsExist={state.areJobsRunning}
disabled={
xoaUpdaterState !== 'upgradeNeeded' &&
xoaTrialState.state !== 'untrustedTrial'
@@ -276,6 +322,56 @@ const Updates = decorate([
</CardBlock>
</Card>
</Col>
<Col mediumSize={6}>
<Card>
<CardHeader>{_('releaseChannels')}</CardHeader>
<CardBlock>
<form id={state.channelsFormId} className='form'>
<div className='form-group'>
<Select
isLoading={state.channelsOptions === undefined}
onChange={effects.onChannelChange}
options={state.channelsOptions}
placeholder={formatMessage(messages.selectChannel)}
required
simpleValue
value={
state.isUnlistedChannel
? UNLISTED_CHANNEL_VALUE
: state.consolidatedChannel
}
/>
<br />
{state.isUnlistedChannel && (
<div className='form-group'>
<DebounceInput
autoFocus
className='form-control'
debounceTimeout={500}
name='channel'
onChange={effects.linkState}
placeholder={formatMessage(
messages.unlistedChannelName
)}
required
type='text'
value={state.consolidatedChannel}
/>
</div>
)}
</div>{' '}
<ActionButton
btnStyle='primary'
form={state.channelsFormId}
handler={effects.configure}
icon='success'
>
{_('changeChannel')}
</ActionButton>
</form>
</CardBlock>
</Card>
</Col>
</Row>
<Row>
<Col mediumSize={6}>

View File

@@ -14,6 +14,7 @@ import { every, filter, find, flatten, forEach, isEmpty, map } from 'lodash'
import { get } from '@xen-orchestra/defined'
import {
addSubscriptions,
adminOnly,
connectStore,
cowSet,
formatSize,
@@ -211,6 +212,7 @@ const XOSAN_INDIVIDUAL_ACTIONS = [
},
]
@adminOnly
@connectStore(() => {
const getHosts = createGetObjectsOfType('host')
const getHostsByPool = getHosts.groupBy('$pool')

View File

@@ -301,7 +301,7 @@ export default class NewXosan extends Component {
}
render() {
if (process.env.XOA_PLAN === 5) {
if (+process.env.XOA_PLAN === 5) {
return (
<em>
{_('xosanSourcesDisclaimer', {

1369
yarn.lock

File diff suppressed because it is too large Load Diff