Compare commits

..

75 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
badrAZ
c2ef0ded43 feat(xo-server): 5.41.0 2019-05-14 16:20:09 +02:00
badrAZ
6081a6f6db feat(vhd-lib): 0.7.0 2019-05-14 16:19:15 +02:00
badrAZ
a0d92a0b1d feat(@xen-orchestra/fs): 0.9.0 2019-05-14 16:17:19 +02:00
badrAZ
3cf1f7ede2 feat(xo-server-backup-reports): 0.16.1 2019-05-14 16:15:29 +02:00
Julien Fontanet
5757afa1d8 fix(xo-server/proxies): use Host of the config
Use the hostname of the URL in the config instead of the one from the incoming request.
2019-05-14 14:08:04 +02:00
Julien Fontanet
86e9b9c1b8 fix(xo-server/api/*): wrap with JsonRpcError 2019-05-14 10:21:43 +02:00
Danp2
1cdd1fa00e Switch forum link to new XCP subforum (#4203)
* Fix usage of "backuping"

* Switch to XCP sub-forum
2019-05-10 17:49:26 +02:00
Julien Fontanet
9d12759c68 fix(xo-server/VM import): forward error message 2019-05-10 17:32:23 +02:00
Julien Fontanet
594341fab6 fix(xo-server/remotes): dont fail is benchmarks JSON is broken 2019-05-09 14:06:50 +02:00
Julien Fontanet
4e88125cbe fix(xo-server/remotes): always JSON encode benchmarks (#4197)
See xoa-support#1464

Fix an issue when restoring from config.
2019-05-09 13:58:36 +02:00
badrAZ
13237180a2 fix(Backup-ng): report sent even though "Never" is selected (#4178)
Fixes #4092
2019-05-09 09:58:35 +02:00
Olivier Lambert
f64d7e0b6e fix README title and also update installation doc (#4198) 2019-05-09 09:34:36 +02:00
Olivier Lambert
040a6930a4 improving support page (#4196)
* improving support page
2019-05-09 09:27:26 +02:00
Julien Fontanet
c54b9189a6 feat(scripts/normalize-package): add version if missing 2019-05-09 09:23:49 +02:00
Julien Fontanet
8882f1b019 chore(bump-pkg): stop on errors 2019-05-07 14:39:17 +02:00
Julien Fontanet
ae6416c4d2 feat(xo-server): add XenServers from XenStore (#4194)
XenStore can be used to pass XenServers to connect to to xo-server:

`vm-data/xen-servers` entry:

```json
[
  {
    "allowUnauthorized": true,
    "host": "xs1.company.tld",
    "label": "My XenServer",
    "password": "super%secret+password",
    "readOnly": false,
    "username": "root"
  }
]
```
2019-05-07 10:25:00 +02:00
Dustin B
8faed87656 Documentation typo
Typo "on" to "one" in support page
2019-05-06 22:41:37 +02:00
Dustin B
0983f05969 Update support documentation 2019-05-06 22:27:57 +02:00
Julien Fontanet
d43e2544a1 feat(xo-server): admin account from XenStore (#4184)
XenStore can be used to pass the credentials of the default admin account to xo-server:

`vm-data/admin-account` entry:

```json
{ "email": "admin@admin.net", "password": "admin" }
```
2019-05-06 17:40:02 +02:00
Pierre Donias
ca83d11ac8 fix(xo-server/xapi-object-to-xo): cast patch size to number (#4193)
Fixes #4192
2019-05-06 16:55:54 +02:00
badrAZ
1cdcdd9b5f chore(reaclette): v0.8.0 (#4188) 2019-05-06 15:03:08 +02:00
Julien Fontanet
cc7806e35b chore(xo-server plugins): are not published as well 2019-05-06 11:45:00 +02:00
Julien Fontanet
0ee48b6623 chore(xo-{server,web}): mark as private
Explicit that these packages are not published on the npm registry.
2019-05-06 11:32:55 +02:00
Julien Fontanet
8c02e0efbd chore(scripts): publish on version bumping 2019-05-06 11:32:55 +02:00
marcpezin
34d3ca82bc adding free limited support section (#4186) 2019-05-06 10:21:18 +02:00
HamadaBrest
43822d3667 chore(vhd-lib): use write instead of createOutputStream (#4179)
See #4156
2019-05-03 14:07:10 +02:00
Pierre Donias
f4ac73b3b4 fix(xo-web/new-vm): cloud init should only be available in Premium (#4174) 2019-05-02 17:01:06 +02:00
HamadaBrest
f084b6def9 feat(fs#write): write buffer at specific position (#4169)
Fixes #4156
2019-05-02 16:20:57 +02:00
Julien Fontanet
a00d101ff7 chore: update dependencies 2019-05-02 10:58:38 +02:00
badrAZ
9d5900d9b6 chore(CHANGELOG): 5.34.0 2019-04-30 11:39:03 +02:00
117 changed files with 2487 additions and 2101 deletions

View File

@@ -46,6 +46,7 @@
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepare": "yarn run build",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -17,5 +17,8 @@
"dependencies": {
"golike-defer": "^0.4.1",
"xen-api": "^0.25.1"
},
"scripts": {
"postversion": "npm publish"
}
}

View File

@@ -55,6 +55,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run clean",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -43,6 +43,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -42,6 +42,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@xen-orchestra/fs",
"version": "0.8.0",
"version": "0.9.0",
"license": "AGPL-3.0",
"description": "The File System for Xen Orchestra backups.",
"keywords": [],
@@ -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"
},
@@ -55,6 +55,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run clean",
"prepare": "yarn run build"
"prepare": "yarn run build",
"postversion": "npm publish"
}
}

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)
@@ -410,6 +414,18 @@ export default class RemoteHandlerAbstract {
await this._unlink(file).catch(ignoreEnoent)
}
async write(
file: File,
buffer: Buffer,
position: number
): Promise<{| bytesWritten: number, buffer: Buffer |}> {
await this._write(
typeof file === 'string' ? normalizePath(file) : file,
buffer,
position
)
}
async writeFile(
file: string,
data: Data,
@@ -546,6 +562,28 @@ export default class RemoteHandlerAbstract {
throw new Error('Not implemented')
}
async _write(file: File, buffer: Buffer, position: number): Promise<void> {
const isPath = typeof file === 'string'
if (isPath) {
file = await this.openFile(file, 'r+')
}
try {
return await this._writeFd(file, buffer, position)
} finally {
if (isPath) {
await this.closeFile(file)
}
}
}
async _writeFd(
fd: FileDescriptor,
buffer: Buffer,
position: number
): Promise<void> {
throw new Error('Not implemented')
}
async _writeFile(
file: string,
data: Data,

View File

@@ -3,9 +3,9 @@
import 'dotenv/config'
import asyncIteratorToStream from 'async-iterator-to-stream'
import getStream from 'get-stream'
import { forOwn, random } from 'lodash'
import { fromCallback } from 'promise-toolbox'
import { pipeline } from 'readable-stream'
import { random } from 'lodash'
import { tmpdir } from 'os'
import { getHandler } from '.'
@@ -310,5 +310,70 @@ handlers.forEach(url => {
await handler.unlink('file')
})
})
describe('#write()', () => {
beforeEach(() => handler.outputFile('file', TEST_DATA))
const PATCH_DATA_LEN = Math.ceil(TEST_DATA_LEN / 2)
const PATCH_DATA = unsecureRandomBytes(PATCH_DATA_LEN)
forOwn(
{
'dont increase file size': (() => {
const offset = random(0, TEST_DATA_LEN - PATCH_DATA_LEN)
const expected = Buffer.from(TEST_DATA)
PATCH_DATA.copy(expected, offset)
return { offset, expected }
})(),
'increase file size': (() => {
const offset = random(
TEST_DATA_LEN - PATCH_DATA_LEN + 1,
TEST_DATA_LEN
)
const expected = Buffer.alloc(offset + PATCH_DATA_LEN)
TEST_DATA.copy(expected)
PATCH_DATA.copy(expected, offset)
return { offset, expected }
})(),
},
({ offset, expected }, title) => {
describe(title, () => {
testWithFileDescriptor('file', 'r+', async ({ file }) => {
await handler.write(file, PATCH_DATA, offset)
await expect(await handler.readFile('file')).toEqual(expected)
})
})
}
)
})
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,10 +106,18 @@ 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))
}
_writeFd(file, buffer, position) {
return fs.write(file.fd, buffer, 0, buffer.length, position)
}
_writeFile(file, data, { flags }) {
return fs.writeFile(this._getFilePath(file), data, { flag: flags })
}

View File

@@ -155,10 +155,20 @@ 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)
}
_writeFd(file, buffer, position) {
return this._client.write(file.fd, buffer, 0, buffer.length, position)
}
_writeFile(file, data, options) {
return this._client
.writeFile(this._getFilePath(file), data, options)

View File

@@ -48,6 +48,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -45,6 +45,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -1,6 +1,24 @@
# ChangeLog
## **next** (2019-04-26)
## **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
@@ -39,7 +57,7 @@
- xo-vmdk-to-vhd v0.1.7
- vhd-lib v0.6.1
- xo-server v5.40.0
- xo-web v5.40.0
- xo-web v5.40.1
## **5.33.1** (2019-04-04)

View File

@@ -2,9 +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
- [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 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

@@ -1,4 +1,4 @@
# Xen Orchestra [![Chat with us](https://storage.crisp.im/plugins/images/936925df-f37b-4ba8-bab0-70cd2edcb0be/badge.svg)](https://go.crisp.im/chat/embed/?website_id=-JzqzzwddSV7bKGtEyAQ) [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
# Xen Orchestra [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
![](http://i.imgur.com/tRffA5y.png)

View File

@@ -1,13 +1,13 @@
# Installation
SSH to your XenServer and execute the following:
SSH to your XenServer/XCP-ng host and execute the following:
```
bash -c "$(curl -s http://xoa.io/deploy)"
```
This will automatically download/import/start the XOA appliance. Nothing is changed on your XenServer host itself, it's 100% safe.
This will automatically download/import/start the XOA appliance. Nothing is changed on your host itself, it's 100% safe.
## [More on XOA](xoa.md)

View File

@@ -1,24 +1,33 @@
# Support
You can access our pro support if you subscribe to any of these plans:
Xen Orchestra will run in a controlled/tested environment thanks to XOA ([Xen Orchestra virtual Appliance](https://xen-orchestra.com/#!/xoa)). **This is the way to get pro support**. Any account with a registered XOA can access a [dedicated support panel](https://xen-orchestra.com/#!/member/support).
XOA is available in multiple plans:
* Free
* Starter
* Enterprise
* Premium
The better the plan, the faster the support will be with higher priority.
Higher tier support plans include faster ticket response times (and cover more features). Paid support plans and response times are based on the plan you have, plans can be [reviewed here](https://xen-orchestra.com/#!/xo-pricing).
## XOA Free support
With the free version of the Xen Orchestra Appliance (XOA free), you can open support tickets and we will do our best to assist you, however, this support is limited and is not guaranteed in regards to response times or resolutions offered.
## Community support
If you are using Xen Orchestra via the sources, you can ask questions and try to recieve help two different ways:
If you are using Xen Orchestra via the source and not XOA, you can ask questions and try to recieve help through a number of different ways:
* In our [forum](https://xen-orchestra.com/forum/)
* In our [forum](https://xcp-ng.org/forum/category/12/xen-orchestra)
* In our IRC - `#xen-orchestra` on `Freenode`
However, there's no guarantee you will receive an answer and no guaranteed response time. If you are using XO from sources, we encourage you to give back to the community by assisting other users via these two avenues as well.
We encourage you to give back to the community by assisting other users via these two avenues as well.
If you are using Xen Orchestra in production, please subscribe to a plan.
Lastly while Xen Orchestra is free and Open Source software, supporting and developing it takes a lot of effort. If you are considering using Xen Orchestra in production, please subscribe for one of our [professional support plans](https://xen-orchestra.com/#!/xo-pricing).
> Note: support from the sources is harder, because Xen Orchestra can potentially run on any Linux distro (or even FreeBSD and Windows!). Always try to double check that you followed our guide on how to [install it from the sources](https://xen-orchestra.com/docs/from_the_sources.html) before going further.
## Open a ticket
If you have a subscription, you can open a ticket describing your issue directly from your personal account page [here](https://xen-orchestra.com/#!/member/support)
If you have a subscription (or at least a registered free XOA), you can open a ticket describing your issue directly from your personal account page [here](https://xen-orchestra.com/#!/member/support)

View File

@@ -12,14 +12,14 @@
"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",
"exec-promise": "^0.7.0",
"flow-bin": "^0.97.0",
"flow-bin": "^0.98.0",
"globby": "^9.0.0",
"husky": "^1.2.1",
"husky": "^2.2.0",
"jest": "^24.1.0",
"lodash": "^4.17.4",
"prettier": "^1.10.2",

View File

@@ -44,6 +44,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -43,6 +43,7 @@
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepare": "yarn run build",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -27,12 +27,12 @@
"node": ">=6"
},
"dependencies": {
"@xen-orchestra/fs": "^0.8.0",
"@xen-orchestra/fs": "^0.9.0",
"cli-progress": "^2.0.0",
"exec-promise": "^0.7.0",
"getopts": "^2.2.3",
"struct-fu": "^1.2.0",
"vhd-lib": "^0.6.1"
"vhd-lib": "^0.7.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
@@ -51,6 +51,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "rimraf dist/ && index-modules --cjs-lazy src/commands",
"predev": "yarn run prebuild",
"prepare": "yarn run build"
"prepare": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "vhd-lib",
"version": "0.6.1",
"version": "0.7.0",
"license": "AGPL-3.0",
"description": "Primitives for VHD file handling",
"keywords": [],
@@ -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",
@@ -35,7 +35,7 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"@xen-orchestra/fs": "^0.8.0",
"@xen-orchestra/fs": "^0.9.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"execa": "^1.0.0",
@@ -52,6 +52,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run clean",
"prepare": "yarn run build"
"prepare": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -1,9 +1,7 @@
import assert from 'assert'
import { fromEvent } from 'promise-toolbox'
import checkFooter from './_checkFooter'
import checkHeader from './_checkHeader'
import constantStream from './_constant-stream'
import getFirstAndLastBlocks from './_getFirstAndLastBlocks'
import { fuFooter, fuHeader, checksumStruct, unpackField } from './_structs'
import { set as mapSetBit, test as mapTestBit } from './_bitmap'
@@ -232,24 +230,11 @@ export default class Vhd {
// Write functions.
// =================================================================
// Write a buffer/stream at a given position in a vhd file.
// Write a buffer at a given position in a vhd file.
async _write(data, offset) {
debug(
`_write offset=${offset} size=${
Buffer.isBuffer(data) ? data.length : '???'
}`
)
// TODO: could probably be merged in remote handlers.
const stream = await this._handler.createOutputStream(this._path, {
flags: 'r+',
start: offset,
})
return Buffer.isBuffer(data)
? new Promise((resolve, reject) => {
stream.on('error', reject)
stream.end(data, resolve)
})
: fromEvent(data.pipe(stream), 'finish')
assert(Buffer.isBuffer(data))
debug(`_write offset=${offset} size=${data.length}`)
return this._handler.write(this._path, data, offset)
}
async _freeFirstBlockSpace(spaceNeededBytes) {
@@ -306,7 +291,7 @@ export default class Vhd {
`ensureBatSize: extend BAT ${prevMaxTableEntries} -> ${maxTableEntries}`
)
await this._write(
constantStream(BUF_BLOCK_UNUSED, maxTableEntries - prevMaxTableEntries),
Buffer.alloc(maxTableEntries - prevMaxTableEntries, BUF_BLOCK_UNUSED),
header.tableOffset + prevBat.length
)
await this.writeHeader()
@@ -331,10 +316,7 @@ export default class Vhd {
await Promise.all([
// Write an empty block and addr in vhd file.
this._write(
constantStream([0], this.fullBlockSize),
sectorsToBytes(blockAddr)
),
this._write(Buffer.alloc(this.fullBlockSize), sectorsToBytes(blockAddr)),
this._setBatEntry(blockId, blockAddr),
])

View File

@@ -56,6 +56,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -69,6 +69,7 @@
"plot": "gnuplot -p memory-test.gnu",
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -25,5 +25,8 @@
},
"dependencies": {
"xo-common": "^0.2.0"
},
"scripts": {
"postversion": "npm publish"
}
}

View File

@@ -64,6 +64,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -43,6 +43,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -45,6 +45,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

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",
@@ -55,6 +55,7 @@
"lint": "tslint 'src/*.ts'",
"posttest": "yarn run lint",
"prepublishOnly": "yarn run build",
"start": "node dist/index.js"
"start": "node dist/index.js",
"postversion": "npm publish"
}
}

View File

@@ -32,7 +32,7 @@
"node": ">=6"
},
"dependencies": {
"jsonrpc-websocket-client": "^0.4.1",
"jsonrpc-websocket-client": "^0.5.0",
"lodash": "^4.17.2",
"make-error": "^1.0.4"
},
@@ -49,6 +49,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -41,6 +41,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepare": "yarn run build"
"prepare": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -41,5 +41,6 @@
"build": "NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"dev": "NODE_DEV=development babel --watch --source-maps --out-dir=dist/ src/",
"prepublishOnly": "yarn run build"
}
},
"private": true
}

View File

@@ -49,5 +49,6 @@
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
}
},
"private": true
}

View File

@@ -55,5 +55,6 @@
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
}
},
"private": true
}

View File

@@ -50,5 +50,6 @@
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
}
},
"private": true
}

View File

@@ -1,6 +1,6 @@
{
"name": "xo-server-backup-reports",
"version": "0.16.0",
"version": "0.16.1",
"license": "AGPL-3.0",
"description": "Backup reports plugin for XO-Server",
"keywords": [
@@ -58,5 +58,6 @@
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
}
},
"private": true
}

View File

@@ -245,6 +245,9 @@ class BackupReportsXoPlugin {
if (
!force &&
(reportWhen === 'never' ||
// Handle improper value introduced by:
// https://github.com/vatesfr/xen-orchestra/commit/753ee994f2948bbaca9d3161eaab82329a682773#diff-9c044ab8a42ed6576ea927a64c1ec3ebR105
reportWhen === 'Never' ||
(reportWhen === 'failure' && log.status === 'success'))
) {
return

View File

@@ -33,7 +33,7 @@
},
"dependencies": {
"http-request-plus": "^0.8.0",
"jsonrpc-websocket-client": "^0.4.1"
"jsonrpc-websocket-client": "^0.5.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
@@ -49,5 +49,6 @@
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
}
},
"private": true
}

View File

@@ -44,5 +44,6 @@
"build": "NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"dev": "NODE_DEV=development babel --watch --source-maps --out-dir=dist/ src/",
"prepublishOnly": "yarn run build"
}
},
"private": true
}

View File

@@ -42,5 +42,6 @@
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
}
},
"private": true
}

View File

@@ -50,5 +50,6 @@
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
}
},
"private": true
}

View File

@@ -49,5 +49,6 @@
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
}
},
"private": true
}

View File

@@ -50,5 +50,6 @@
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
}
},
"private": true
}

View File

@@ -50,5 +50,6 @@
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
}
},
"private": true
}

View File

@@ -59,5 +59,6 @@
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
}
},
"private": true
}

View File

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

View File

@@ -1,6 +1,7 @@
{
"private": true,
"name": "xo-server",
"version": "5.40.0",
"version": "5.42.0",
"license": "AGPL-3.0",
"description": "Server part of Xen-Orchestra",
"keywords": [
@@ -37,11 +38,11 @@
"@xen-orchestra/cron": "^1.0.3",
"@xen-orchestra/defined": "^0.0.0",
"@xen-orchestra/emit-async": "^0.0.0",
"@xen-orchestra/fs": "^0.8.0",
"@xen-orchestra/fs": "^0.9.0",
"@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",
@@ -50,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",
@@ -64,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",
@@ -109,7 +110,7 @@
"readable-stream": "^3.2.0",
"redis": "^2.8.0",
"schema-inspector": "^1.6.8",
"semver": "^5.4.1",
"semver": "^6.0.0",
"serve-static": "^1.13.1",
"split-lines": "^2.0.0",
"stack-chain": "^2.0.0",
@@ -120,7 +121,7 @@
"tmp": "^0.1.0",
"uuid": "^3.0.1",
"value-matcher": "^0.2.0",
"vhd-lib": "^0.6.1",
"vhd-lib": "^0.7.0",
"ws": "^6.0.0",
"xen-api": "^0.25.1",
"xml2js": "^0.4.19",

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

@@ -0,0 +1,5 @@
import fromCallback from 'promise-toolbox/fromCallback'
import { execFile } from 'child_process'
export const read = key =>
fromCallback(cb => execFile('xenstore-read', [key], cb))

View File

@@ -1,6 +1,6 @@
import createLogger from '@xen-orchestra/log'
import pump from 'pump'
import { format } from 'json-rpc-peer'
import { format, JsonRpcError } from 'json-rpc-peer'
import { noSuchObject } from 'xo-common/api-errors'
import { parseSize } from '../utils'
@@ -128,7 +128,7 @@ async function handleImportContent(req, res, { xapi, id }) {
res.end(format.response(0, true))
} catch (e) {
res.writeHead(500)
res.end(format.error(0, new Error(e.message)))
res.end(format.error(0, new JsonRpcError(e.message)))
}
}

View File

@@ -1,4 +1,4 @@
import { format } from 'json-rpc-peer'
import { format, JsonRpcError } from 'json-rpc-peer'
// ===================================================================
@@ -248,7 +248,7 @@ async function handleInstallSupplementalPack(req, res, { hostId }) {
res.end(format.response(0))
} catch (e) {
res.writeHead(500)
res.end(format.error(0, new Error(e.message)))
res.end(format.error(0, new JsonRpcError(e.message)))
}
}

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

@@ -1,4 +1,4 @@
import { format } from 'json-rpc-peer'
import { format, JsonRPcError } from 'json-rpc-peer'
// ===================================================================
@@ -234,7 +234,7 @@ async function handleInstallSupplementalPack(req, res, { poolId }) {
res.end(format.response(0))
} catch (e) {
res.writeHead(500)
res.end(format.error(0, new Error(e.message)))
res.end(format.error(0, new JsonRPcError(e.message)))
}
}

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

@@ -1,5 +1,5 @@
import defer from 'golike-defer'
import { format } from 'json-rpc-peer'
import { format, JsonRpcError } from 'json-rpc-peer'
import { ignoreErrors } from 'promise-toolbox'
import { assignWith, concat } from 'lodash'
import {
@@ -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 = {
@@ -1203,7 +1197,7 @@ async function handleVmImport(req, res, { data, srId, type, xapi }) {
res.end(format.response(0, vm.$id))
} catch (e) {
res.writeHead(500)
res.end(format.error(0, new Error(e.message)))
res.end(format.error(0, new JsonRpcError(e.message)))
}
}
@@ -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

@@ -417,6 +417,7 @@ const setUpProxies = (express, opts, xo) => {
}
const proxy = createProxyServer({
changeOrigin: true,
ignorePath: true,
}).on('error', error => console.error(error))

View File

@@ -2,6 +2,8 @@ import Collection from '../collection/redis'
import Model from '../model'
import { forEach } from '../utils'
import { parseProp } from './utils'
// ===================================================================
export default class Remote extends Model {}
@@ -14,12 +16,21 @@ export class Remotes extends Collection {
async get(properties) {
const remotes = await super.get(properties)
forEach(remotes, remote => {
remote.benchmarks =
remote.benchmarks !== undefined
? JSON.parse(remote.benchmarks)
: undefined
remote.benchmarks = parseProp('remote', remote, 'benchmarks')
remote.enabled = remote.enabled === 'true'
})
return remotes
}
_update(remotes) {
return super._update(
remotes.map(remote => {
const { benchmarks } = remote
if (benchmarks !== undefined) {
remote.benchmarks = JSON.stringify(benchmarks)
}
return remote
})
)
}
}

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'),
}
@@ -633,7 +643,7 @@ const TRANSFORMS = {
description: poolPatch.name_description,
name: poolPatch.name_label,
pool_patch: poolPatch.$ref,
size: poolPatch.size,
size: +poolPatch.size,
guidance: poolPatch.after_apply_guidance,
time: toTimestamp(obj.timestamp_applied),

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
@@ -168,7 +170,7 @@ export default class {
}
@synchronized()
async _updateRemote(id, { benchmarks, url, ...props }) {
async _updateRemote(id, { url, ...props }) {
const remote = await this._getRemote(id)
// url is handled separately to take care of obfuscated values
@@ -176,13 +178,6 @@ export default class {
remote.url = format(sensitiveValues.merge(parse(url), parse(remote.url)))
}
if (
benchmarks !== undefined ||
(benchmarks = remote.benchmarks) !== undefined
) {
remote.benchmarks = JSON.stringify(benchmarks)
}
patch(remote, props)
return (await this._remotes.update(remote)).properties

View File

@@ -4,6 +4,7 @@ import { ignoreErrors } from 'promise-toolbox'
import { hash, needsRehash, verify } from 'hashy'
import { invalidCredentials, noSuchObject } from 'xo-common/api-errors'
import * as XenStore from '../_XenStore'
import { Groups } from '../models/group'
import { Users } from '../models/user'
import { forEach, isEmpty, lightSet, mapToArray } from '../utils'
@@ -68,8 +69,12 @@ export default class {
)
if (!(await usersDb.exists())) {
const email = 'admin@admin.net'
const password = 'admin'
const {
email = 'admin@admin.net',
password = 'admin',
} = await XenStore.read('vm-data/admin-account')
.then(JSON.parse)
.catch(() => ({}))
await this.createUser({ email, password, permission: 'admin' })
log.info(`Default user created: ${email} with password ${password}`)

View File

@@ -4,6 +4,7 @@ import { fibonacci } from 'iterable-backoff'
import { noSuchObject } from 'xo-common/api-errors'
import { pDelay, ignoreErrors } from 'promise-toolbox'
import * as XenStore from '../_XenStore'
import Xapi from '../xapi'
import xapiObjectToXo from '../xapi-object-to-xo'
import XapiStats from '../xapi-stats'
@@ -64,8 +65,19 @@ export default class {
servers => serversDb.update(servers)
)
// Connects to existing servers.
const servers = await serversDb.get()
// Add servers in XenStore
if (servers.length === 0) {
const xenStoreServers = await XenStore.read('vm-data/xen-servers')
.then(JSON.parse)
.catch(() => [])
for (const server of xenStoreServers) {
servers.push(await this.registerXenServer(server))
}
}
// Connects to existing servers.
for (const server of servers) {
if (server.enabled) {
this.connectXenServer(server.id).catch(error => {
@@ -374,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))
@@ -445,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

@@ -29,7 +29,7 @@
"pipette": "^0.9.3",
"promise-toolbox": "^0.12.1",
"tmp": "^0.1.0",
"vhd-lib": "^0.6.1"
"vhd-lib": "^0.7.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
@@ -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"
@@ -50,6 +50,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run clean",
"prepare": "yarn run build"
"prepare": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -1,7 +1,7 @@
{
"private": false,
"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",
@@ -100,7 +100,7 @@
"prop-types": "^15.6.0",
"qrcode": "^1.3.2",
"random-password": "^0.1.2",
"reaclette": "^0.7.0",
"reaclette": "^0.8.0",
"react": "^15.4.1",
"react-addons-shallow-compare": "^15.6.2",
"react-addons-test-utils": "^15.6.2",

View File

@@ -87,3 +87,5 @@ export const DEFAULT_NETWORK_CONFIG_TEMPLATE = `#network:
# name: eth0
# subnets:
# - type: dhcp`
export const CAN_CLOUD_INIT = +process.env.XOA_PLAN > 3

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 -----
@@ -1321,6 +1271,7 @@ const messages = {
newVmSshKey: 'SSH key',
noConfigDrive: 'No config drive',
newVmCustomConfig: 'Custom config',
premiumOnly: 'Only available in Premium',
availableTemplateVarsInfo:
'Click here to see the available template variables',
availableTemplateVarsTitle: 'Available template variables',
@@ -1338,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:',
@@ -1378,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',
@@ -1392,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})',
@@ -1456,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:
@@ -1465,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',
@@ -1476,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',
@@ -1514,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}}?',
@@ -1529,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}):',
@@ -1670,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 ?',
@@ -1706,7 +1639,6 @@ const messages = {
serverHost: 'Host',
serverUsername: 'Username',
serverPassword: 'Password',
serverAction: 'Action',
serverReadOnly: 'Read Only',
serverUnauthorizedCertificates: 'Unauthorized Certificates',
serverAllowUnauthorizedCertificates: 'Allow Unauthorized Certificates',
@@ -1722,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',
@@ -1734,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',
@@ -1785,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',
@@ -1816,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',
@@ -1860,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',
@@ -1906,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.',
@@ -1933,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',
@@ -1972,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',
@@ -2026,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',
@@ -2038,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.',
@@ -2057,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}.',
@@ -2066,7 +1990,6 @@ const messages = {
xosanHost: 'Host',
xosanHosts: 'Connected Hosts',
xosanPool: 'Pool',
xosanVolumeId: 'Volume ID',
xosanSize: 'Size',
xosanUsedSpace: 'Used space',
xosanLicense: 'License',
@@ -2077,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',
@@ -2086,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',
@@ -2103,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!",
@@ -2121,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
@@ -2168,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

@@ -37,7 +37,9 @@ export class TooltipViewer extends Component {
return (
<div
className={classNames(
show ? styles.tooltipEnabled : styles.tooltipDisabled,
show && content !== undefined
? styles.tooltipEnabled
: styles.tooltipDisabled,
className
)}
style={{

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

@@ -88,7 +88,7 @@ export default class About extends Component {
<p className='text-muted'>{_('bugTrackerText')}</p>
</Col>
<Col mediumSize={6}>
<a href='https://xen-orchestra.com/forum/'>
<a href='https://xcp-ng.org/forum/category/12/xen-orchestra'>
<Icon icon='group' size={4} />
<h4>{_('community')}</h4>
</a>

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')
)
)
)

Some files were not shown because too many files have changed in this diff Show More