Compare commits

..

88 Commits

Author SHA1 Message Date
Pierre Donias
15e96e0921 feat: add Visualization view 2019-07-17 10:54:15 +02:00
Julien Fontanet
9644cabbac feat(lint-staged): enable for jsx, ts and tsx files 2019-05-29 15:44:23 +02:00
Julien Fontanet
c4eeb2b77e connection to xo-server 2019-05-29 13:57:43 +02:00
Julien Fontanet
ee1e9ba7b7 typescript 2019-05-29 13:57:42 +02:00
Julien Fontanet
3655117a13 feat(xo-web-6): initial config 2019-05-29 13:57:41 +02:00
Julien Fontanet
cc2c71c076 chore(xo-server): remove unnecessary methods 2019-05-28 18:53:57 +02:00
Pierre Donias
9ca273b2c4 chore(CHANGELOG.unreleased): add some missing links (#4253) 2019-05-28 14:09:19 +02:00
Julien Fontanet
b85c2f35b6 fix(xo-server/vm.set): autoPoweronauto_poweron in XAPI
Related to 0e1e32d241
2019-05-28 11:48:00 +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
badrAZ
28fb4e8216 feat(xo-web): 5.40.1 2019-04-30 11:21:14 +02:00
Julien Fontanet
bec4dbe652 feat(xo-web): unlock basic stats for all editions (#4172)
Fixes #4166
2019-04-30 10:17:31 +02:00
Pierre Donias
72cc14f508 feat(xo-web): add banner to clarify support conditions (#4167)
Fixes #4165
2019-04-30 09:54:40 +02:00
Julien Fontanet
d20941cc2c chore(CHANGELOG): Highlights sections 2019-04-29 14:10:27 +02:00
Julien Fontanet
9cb8a05316 chore(CHANGELOG): update next 2019-04-26 16:31:26 +02:00
Julien Fontanet
dccd799f6d feat(xo-web): 5.40.0 2019-04-26 16:27:25 +02:00
143 changed files with 254899 additions and 2350 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

@@ -23,14 +23,20 @@ const configs = {
shippedProposals: true,
targets: (() => {
const targets = {}
const browsers = pkg.browserslist
if (browsers !== undefined) {
targets.browsers = browsers
}
let node = (pkg.engines || {}).node
if (node !== undefined) {
const trimChars = '^=>~'
while (trimChars.includes(node[0])) {
node = node.slice(1)
}
targets.node = node
}
return { browsers: pkg.browserslist, node }
return targets
})(),
useBuiltIns: '@babel/polyfill' in (pkg.dependencies || {}) && 'usage',
}

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,32 @@
# ChangeLog
## **next** (2019-04-11)
## **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
- [Self/New VM] Add network config box to custom cloud-init [#3872](https://github.com/vatesfr/xen-orchestra/issues/3872) (PR [#4150](https://github.com/vatesfr/xen-orchestra/pull/4150))
- [Metadata backup] Detailed logs [#4005](https://github.com/vatesfr/xen-orchestra/issues/4005) (PR [#4014](https://github.com/vatesfr/xen-orchestra/pull/4014))
- [Backup reports] Support metadata backups (PR [#4084](https://github.com/vatesfr/xen-orchestra/pull/4084))
- [VM migration] Auto select default SR and collapse optional actions [#3326](https://github.com/vatesfr/xen-orchestra/issues/3326) (PR [#4121](https://github.com/vatesfr/xen-orchestra/pull/4121))
- Unlock basic stats on all editions [#4166](https://github.com/vatesfr/xen-orchestra/issues/4166) (PR [#4172](https://github.com/vatesfr/xen-orchestra/pull/4172))
### Enhancements
@@ -9,8 +35,11 @@
- [Import] Avoid blocking the UI when dropping a big OVA file on the UI (PR [#4018](https://github.com/vatesfr/xen-orchestra/pull/4018))
- [Backup NG/Overview] Make backup list title clearer [#4111](https://github.com/vatesfr/xen-orchestra/issues/4111) (PR [#4129](https://github.com/vatesfr/xen-orchestra/pull/4129))
- [Dashboard] Hide "Report" section for non-admins [#4123](https://github.com/vatesfr/xen-orchestra/issues/4123) (PR [#4126](https://github.com/vatesfr/xen-orchestra/pull/4126))
- [VM migration] Auto select default SR and collapse optional actions [#3326](https://github.com/vatesfr/xen-orchestra/issues/3326) (PR [#4121](https://github.com/vatesfr/xen-orchestra/pull/4121))
- [Metadata backup] Logs [#4005](https://github.com/vatesfr/xen-orchestra/issues/4005) (PR [#4014](https://github.com/vatesfr/xen-orchestra/pull/4014))
- [Self/New VM] Display confirmation modal when user will use a large amount of resources [#4044](https://github.com/vatesfr/xen-orchestra/issues/4044) (PR [#4127](https://github.com/vatesfr/xen-orchestra/pull/4127))
- [VDI migration, New disk] Warning when SR host is different from the other disks [#3911](https://github.com/vatesfr/xen-orchestra/issues/3911) (PR [#4035](https://github.com/vatesfr/xen-orchestra/pull/4035))
- [Attach disk] Display warning message when VDI SR is on different host from the other disks [#3911](https://github.com/vatesfr/xen-orchestra/issues/3911) (PR [#4117](https://github.com/vatesfr/xen-orchestra/pull/4117))
- [Editable] Notify user when editable undo fails [#3799](https://github.com/vatesfr/xen-orchestra/issues/3799) (PR [#4150](https://github.com/vatesfr/xen-orchestra/pull/4157))
- [XO] Add banner for sources users to clarify support conditions [#4165](https://github.com/vatesfr/xen-orchestra/issues/4165) (PR [#4167](https://github.com/vatesfr/xen-orchestra/pull/4167))
### Bug fixes
@@ -18,13 +47,17 @@
- [Backup NG] Only display full backup interval in case of a delta backup (PR [#4125](https://github.com/vatesfr/xen-orchestra/pull/4107))
- [Dashboard/Health] fix 'an error has occurred' on the storage state table [#4128](https://github.com/vatesfr/xen-orchestra/issues/4128) (PR [#4132](https://github.com/vatesfr/xen-orchestra/pull/4132))
- [Menu] XOA: Fixed empty slot when menu is collapsed [#4012](https://github.com/vatesfr/xen-orchestra/issues/4012) (PR [#4068](https://github.com/vatesfr/xen-orchestra/pull/4068)
- [Self/New VM] Fix missing templates when refreshing page [#3265](https://github.com/vatesfr/xen-orchestra/issues/3265) (PR [#3565](https://github.com/vatesfr/xen-orchestra/pull/3565))
- [Home] No more false positives when select Tag on Home page [#4087](https://github.com/vatesfr/xen-orchestra/issues/4087) (PR [#4112](https://github.com/vatesfr/xen-orchestra/pull/4112))
### Released packages
- xo-server-backup-reports v0.16.0
- complex-matcher v0.6.0
- xo-vmdk-to-vhd v0.1.7
- vhd-lib v0.6.1
- xo-server v5.39.0
- xo-web v5.39.1
- xo-server v5.40.0
- xo-web v5.40.1
## **5.33.1** (2019-04-04)

View File

@@ -2,21 +2,24 @@
### Enhancements
- [Self/New VM] Display confirmation modal when user will use a large amount of resources [#4044](https://github.com/vatesfr/xen-orchestra/issues/4044) (PR [#4127](https://github.com/vatesfr/xen-orchestra/pull/4127))
- [Home] No more false positives when select Tag on Home page [#4087](https://github.com/vatesfr/xen-orchestra/issues/4087) (PR [#4112](https://github.com/vatesfr/xen-orchestra/pull/4112))
- [VDI migration, New disk] Warning when SR host is different from the other disks [#3911](https://github.com/vatesfr/xen-orchestra/issues/3911) (PR [#4035](https://github.com/vatesfr/xen-orchestra/pull/4035))
- [Backup reports] Support metadata backups (PR [#4084](https://github.com/vatesfr/xen-orchestra/pull/4084))
- [Attach disk] Display warning message when VDI SR is on different host from the other disks [#3911](https://github.com/vatesfr/xen-orchestra/issues/3911) (PR [#4117](https://github.com/vatesfr/xen-orchestra/pull/4117))
- [Self/New VM] Add network config box to custom cloud-init [#3872](https://github.com/vatesfr/xen-orchestra/issues/3872) (PR [#4150](https://github.com/vatesfr/xen-orchestra/pull/4150))
- [Editable] Notify user when editable undo fails [#3799](https://github.com/vatesfr/xen-orchestra/issues/3799) (PR [#4150](https://github.com/vatesfr/xen-orchestra/pull/4157))
- [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
- [Self/New VM] Fix missing templates when refreshing page [#3265](https://github.com/vatesfr/xen-orchestra/issues/3265) (PR [#3565](https://github.com/vatesfr/xen-orchestra/pull/3565))
- [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 (PR [#4220](https://github.com/vatesfr/xen-orchestra/pull/4220))
- [Upgrade] Fix alert before upgrade while running backup jobs [#4164](https://github.com/vatesfr/xen-orchestra/issues/4164) (PR [#4235](https://github.com/vatesfr/xen-orchestra/pull/4235))
- [Import] Fix import OVA files (PR [#4232](https://github.com/vatesfr/xen-orchestra/pull/4232))
- [VM/network] Fix duplicate IPv4 (PR [#4239](https://github.com/vatesfr/xen-orchestra/pull/4239))
- [Remotes] Fix disconnected remotes which may appear to work
- [Host] Fix incorrect hypervisor name [#4246](https://github.com/vatesfr/xen-orchestra/issues/4246) (PR [#4248](https://github.com/vatesfr/xen-orchestra/pull/4248))
### Released packages
- xo-server-backup-reports v0.16.0
- complex-matcher v0.6.0
- xo-server v5.40.0
- xo-web v5.40.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,26 +1,22 @@
import { format } from 'json-rpc-peer'
import { format, JsonRpcError } from 'json-rpc-peer'
// ===================================================================
export async function set({
host,
multipathing,
// TODO: use camel case.
multipathing,
name_label: nameLabel,
name_description: nameDescription,
}) {
const xapi = this.getXapi(host)
const hostId = host._xapiId
host = this.getXapiObject(host)
if (multipathing !== undefined) {
await xapi.setHostMultipathing(hostId, multipathing)
}
return xapi.setHostProperties(hostId, {
nameLabel,
nameDescription,
})
await Promise.all([
nameDescription !== undefined && host.set_name_description(nameDescription),
nameLabel !== undefined && host.set_name_label(nameLabel),
multipathing !== undefined &&
host.$xapi.setHostMultipathing(host.$id, multipathing),
])
}
set.description = 'changes the properties of an host'
@@ -248,7 +244,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

@@ -85,18 +85,26 @@ createBonded.description =
// ===================================================================
export async function set({
network,
automatic,
defaultIsLocked,
name_description: nameDescription,
name_label: nameLabel,
network,
}) {
await this.getXapi(network).setNetworkProperties(network._xapiId, {
automatic,
defaultIsLocked,
nameDescription,
nameLabel,
})
network = this.getXapiObject(network)
await Promise.all([
automatic !== undefined &&
network.update_other_config('automatic', automatic ? 'true' : null),
defaultIsLocked !== undefined &&
network.set_default_locking_mode(
defaultIsLocked ? 'disabled' : 'unlocked'
),
nameDescription !== undefined &&
network.set_name_description(nameDescription),
nameLabel !== undefined && network.set_name_label(nameLabel),
])
}
set.params = {

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,18 +1,19 @@
import { format } from 'json-rpc-peer'
import { format, JsonRPcError } from 'json-rpc-peer'
// ===================================================================
export async function set({
pool,
// TODO: use camel case.
name_description: nameDescription,
name_label: nameLabel,
}) {
await this.getXapi(pool).setPoolProperties({
nameDescription,
nameLabel,
})
pool = this.getXapiObject(pool)
await Promise.all([
nameDescription !== undefined && pool.set_name_description(nameDescription),
nameLabel !== undefined && pool.set_name_label(nameLabel),
])
}
set.params = {
@@ -234,7 +235,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

@@ -10,14 +10,15 @@ import { forEach, parseXml } from '../utils'
export async function set({
sr,
// TODO: use camel case.
name_description: nameDescription,
name_label: nameLabel,
}) {
await this.getXapi(sr).setSrProperties(sr._xapiId, {
nameDescription,
nameLabel,
})
sr = this.getXapiObject(sr)
await Promise.all([
nameDescription !== undefined && sr.set_name_description(nameDescription),
nameLabel !== undefined && sr.set_name_label(nameLabel),
])
}
set.params = {
@@ -35,7 +36,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,13 +884,13 @@ 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, {
nameLabel: 'xosan_data',
nameDescription: 'Created by XO',
})
await Promise.all([
vdi.set_name_description('Created by XO'),
vdi.set_name_label('xosan_data'),
])
return vdi
}
}
@@ -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,81 +247,6 @@ 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,
})
}
async setPoolProperties({ autoPoweron, nameLabel, nameDescription }) {
const { pool } = this
await Promise.all([
this._setObjectProperties(pool, {
nameLabel,
nameDescription,
}),
autoPoweron != null &&
this._updateObjectMapProperty(pool, 'other_config', {
autoPoweron: autoPoweron ? 'true' : null,
}),
])
}
async setSrProperties(id, { nameLabel, nameDescription }) {
await this._setObjectProperties(this.getObject(id), {
nameLabel,
nameDescription,
})
}
async setNetworkProperties(
id,
{ automatic, defaultIsLocked, nameDescription, nameLabel }
) {
let defaultLockingMode
if (defaultIsLocked != null) {
defaultLockingMode = defaultIsLocked ? 'disabled' : 'unlocked'
}
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,
}),
])
}
// =================================================================
async addTag(id, tag) {
@@ -345,10 +263,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 +293,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 +311,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 +326,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 +361,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 +374,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 +400,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 +414,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 +485,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 +625,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 +639,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 +976,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 +1002,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 +1097,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 +1158,7 @@ export default class Xapi extends XapiBase {
)
const loop = () =>
this.call(
this.callAsync(
'VM.migrate_send',
vm.$ref,
token,
@@ -1276,7 +1172,7 @@ export default class Xapi extends XapiBase {
pDelay(1e4).then(loop)
)
return loop()
return loop().then(noop)
}
@synchronized()
@@ -1436,9 +1332,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 +1349,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 +1393,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 +1440,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 +1525,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 +1539,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 +1568,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 +1595,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 +1613,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 +1700,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 +1730,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 +1742,7 @@ export default class Xapi extends XapiBase {
type,
virtual_size: size !== undefined ? parseSize(size) : virtual_size,
xenstore_data,
})
}).then(extractOpaqueRef)
)
}
@@ -1886,9 +1760,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 +1776,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 +1796,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 +1809,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 +1823,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 +1831,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 +1857,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 +1866,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 +1917,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 +2045,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 +2073,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 +2112,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 +2127,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 +2158,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 +2167,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,25 +261,15 @@ 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,
}),
value &&
this.setPoolProperties({
autoPoweron: true,
}),
vm.update_other_config('auto_poweron', value ? 'true' : null),
value && vm.$pool.update_other_config('auto_poweron', 'true'),
])
},
},
@@ -292,23 +279,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 +309,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 +338,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 +418,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 +433,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 +444,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 +463,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 +476,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

@@ -0,0 +1,6 @@
const config = (module.exports = require('../../@xen-orchestra/babel-config')(
require('./package.json')
))
// https://webpack.js.org/guides/tree-shaking/
config.presets.find(_ => _[0] === '@babel/preset-env')[1].modules = false

View File

@@ -0,0 +1,47 @@
{
"name": "xo-web-6",
"version": "0.0.0",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-web-6",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "packages/xo-web-6",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"devDependencies": {
"@babel/core": "^7.3.4",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.2.0",
"@babel/plugin-proposal-optional-chaining": "^7.2.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.3.3",
"@emotion/core": "^10.0.10",
"@emotion/styled": "^10.0.10",
"@types/react": "^16.8.13",
"@types/react-dom": "^16.8.4",
"@types/react-helmet": "^5.0.8",
"@types/react-router-dom": "^4.3.2",
"@types/webpack-env": "^1.13.9",
"babel-loader": "^8.0.5",
"clean-webpack-plugin": "^2.0.0",
"cross-env": "^5.2.0",
"html-webpack-plugin": "^3.2.0",
"json-rpc-protocol": "^0.13.1",
"reaclette": "^0.9.0-0",
"react": "^16.8.4",
"react-dom": "^16.8.4",
"react-helmet-async": "^1.0.2",
"react-router-dom": "^5.0.0",
"typescript": "^3.4.4",
"webpack": "^4.29.6",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.2.1"
},
"browserslist": "> 0.5%, last 2 versions, Firefox ESR, not dead",
"scripts": {
"build": "cross-env NODE_ENV=production webpack",
"start": "cross-env NODE_ENV=development webpack-dev-server --hot",
"start:open": "npm run start -- --open"
}
}

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<title>Xen Orchestra</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">The application is loading…</div>
</body>
</html>

View File

@@ -0,0 +1,11 @@
import React from 'react'
import { Helmet } from 'react-helmet-async'
export default () => (
<>
<Helmet>
<title>Bar</title>
</Helmet>
Welcome in Bar
</>
)

View File

@@ -0,0 +1 @@
export default () => 'Foo'

View File

@@ -0,0 +1,8 @@
import React, { Component } from 'react'
import data from './rrd_host_11.json'
export default class Visualization extends Component {
render() {
return <pre>{JSON.stringify(data, null, 2)}</pre>
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
import * as React from 'react'
import styled from '@emotion/styled'
import { HashRouter as Router, Route, Link } from 'react-router-dom'
import { Helmet, HelmetProvider } from 'react-helmet-async'
import { withStore } from 'reaclette'
const Bar = React.lazy(() => import('./Bar'))
const Foo = React.lazy(() => import('./Foo'))
const Visualization = React.lazy(() => import('./Visualization'))
const Title = styled.h1`
color: red;
text-align: center;
`
export default withStore(
{
initialState: () => ({
objects: new Map(),
onNotification: undefined,
}),
effects: {
initialize() {
const onNotification = notification => {
if (notification.message === 'objects') {
// TODO: update `objects`
}
}
this.props.connection.on('notification', onNotification)
this.state.onNotification = onNotification
},
finalize() {
this.props.connection.off('notification', this.state.onNotification)
this.state.onNotification = undefined
},
},
},
(store, props) => (
<Router>
<HelmetProvider>
<Helmet>
<title>Xen Orchestra</title>
</Helmet>
<Title>Xen Orchestra</Title>
<nav>
<ul>
<li>
<Link to='/'>Bar</Link>
</li>
<li>
<Link to='/foo/'>Foo</Link>
</li>
</ul>
</nav>
<React.Suspense fallback='loading'>
<Route path='/' exact component={Bar} />
<Route path='/foo' component={Foo} />
<Route path='/visualization' component={Visualization} />
</React.Suspense>
</HelmetProvider>
</Router>
)

View File

@@ -0,0 +1,11 @@
import React from 'react'
const Button = ({ ...props }) => {
if (props.type === undefined && props.form === undefined) {
props.type = 'button'
}
return <button {...props} />
}
export { Button as default }

View File

@@ -0,0 +1,16 @@
import React, { StrictMode } from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import Connection from './libs/Connection'
ReactDOM.render(
<StrictMode>
<App connection={new Connection()} />
</StrictMode>,
document.getElementById('root')
)
if (module.hot) {
module.hot.accept()
}

View File

@@ -0,0 +1,141 @@
import * as JsonRpc from 'json-rpc-protocol'
import assert from 'assert'
const CONNECTED = 'connected'
const CONNECTING = 'connecting'
const DISCONNECTED = 'disconnected'
function onMessage({ data }) {
const message = JsonRpc.parse(data)
const { type } = message
if (type === 'notification') {
this.emit('notification', message)
} else if (type === 'request') {
this.send(
JsonRpc.format.error(
message.id,
new JsonRpc.MethodNotFound(message.method)
)
)
} else {
const { id } = message
const deferred = this._deferreds[id]
delete this._deferreds[id]
if (type === 'response') {
deferred(message.result)
} else {
assert.strictEqual(type, 'error')
deferred(Promise.reject(message.error))
}
}
}
export default class Connection {
constructor() {
this._deferreds = { __proto__: null }
this._onMessage = onMessage.bind(this)
const url = new URL('./api/')
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
this._url = url.href
this._connected = new Promise(resolve => {
this._resolveConnected = resolve
})
this._disconnected = Promise.resolve()
this._status = DISCONNECTED
}
get connected() {
return this._connected
}
get disconnected() {
return this._disconnected
}
get status() {
return this._status
}
async connect() {
const status = this._status
if (status === CONNECTED) {
return
}
assert.strictEqual(status, DISCONNECTED)
this._status = CONNECTING
try {
ws = this._ws = new WebSocket(this._url)
ws.addEventListener('message', this._onMessage)
await fromEvent(ws, 'open')
await this._call('session.signIn', { token: cookies.get('token') })
this._status = CONNECTED
this._resolveConnected()
this._resolveConnected = undefined
} catch (error) {
ignoreErrors.call(this.disconnect())
throw error
}
}
async disconnect() {
const status = this._status
if (status === DISCONNECTED) {
return
}
if (status === CONNECTED) {
this._connected = new Promise(resolve => {
this._resolveConnected = resolve
})
} else {
assert(status === CONNECTING)
}
const ws = this._ws
this._ws = undefined
ws.close()
this._status = DISCONNECTED
this._resolveDisconnected()
this._resolveDisconnected = undefined
const deferreds = this._deferreds
const ids = Object.keys(deferreds)
if (ids.length !== undefined) {
this._deferreds = { __proto__: null }
const error = Promise.reject(new Error('disconnected'))
ids.forEach(id => {
deferreds[id](error)
})
}
}
call(method, args) {
return method.startsWith('session.')
? Promise.reject(
new Error('session.*() methods are disabled from this interface')
)
: this._call(method, args)
}
_call(method, args) {
const id = Math.random()
.toString(36)
.slice(2)
return new Promise(resolve => {
this._ws.send(JsonRpc.format.request(id, method, args))
this._deferreds[id] = resolve
})
}
}

View File

@@ -0,0 +1,62 @@
{
"compilerOptions": {
/* Basic Options */
"target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
"jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "incremental": true, /* Enable incremental compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
"noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"strictNullChecks": true, /* Enable strict null checks. */
"strictFunctionTypes": true, /* Enable strict checking of function types. */
"strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
"noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
"noUnusedLocals": true, /* Report errors on unused locals. */
"noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
}
}

View File

@@ -0,0 +1,47 @@
const path = require('path')
const resolveApp = relative => path.resolve(__dirname, relative)
const { NODE_ENV = 'production' } = process.env
const __PROD__ = NODE_ENV === 'production'
// https://webpack.js.org/configuration/
module.exports = {
mode: NODE_ENV,
entry: resolveApp('src/index.tsx'),
output: {
filename: __PROD__ ? '[name].[contenthash:8].js' : '[name].js',
path: resolveApp('dist'),
},
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: 'all',
},
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
},
},
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
devtool: __PROD__ ? 'source-map' : 'cheap-module-eval-source-map',
plugins: [
new (require('clean-webpack-plugin'))(),
new (require('html-webpack-plugin'))({
template: resolveApp('public/index.html'),
}),
__PROD__ && new (require('webpack')).HashedModuleIdsPlugin(),
].filter(Boolean),
}

View File

@@ -1,7 +1,7 @@
{
"private": false,
"private": true,
"name": "xo-web",
"version": "5.39.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

@@ -3411,7 +3411,7 @@ export default {
// Original text: "This version is not bundled with any support nor updates. Use it with caution for critical tasks."
disclaimerText3:
'Esta versión no está creada para recibir soporte ni actualizaciones. Úsala con precaución para tareas críticas.',
'Esta versión no está creada para recibir soporte ni actualizaciones. Úsala con precaución.',
// Original text: "Connect PIF"
connectPif: 'Conectar PIF',

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 !',
@@ -3497,7 +3497,7 @@ export default {
// Original text: "This version is not bundled with any support nor updates. Use it with caution for critical tasks."
disclaimerText3:
"Cette version n'est fournie avec aucun support ni aucune mise à jour. Soyez prudent en cas d'utilisation pour des tâches importantes.",
"Cette version n'est fournie avec aucun support ni aucune mise à jour. Utilisez-la avec précaution.",
// Original text: "Connect PIF"
connectPif: 'Connecter la PIF',

View File

@@ -3248,7 +3248,7 @@ export default {
// Original text: "This version is not bundled with any support nor updates. Use it with caution for critical tasks."
disclaimerText3:
'This Verzió is not bundled with any support nor upDates. Use it with caution for critical tasks.',
'This Verzió is not bundled with any support nor upDates. Use it with caution.',
// Original text: "Connect PIF"
connectPif: 'Csatlakozás PIF',

View File

@@ -2958,7 +2958,7 @@ export default {
// Original text: "This version is not bundled with any support nor updates. Use it with caution for critical tasks."
disclaimerText3:
'This version is not bundled with any support nor updates. Use it with caution for critical tasks.',
'This version is not bundled with any support nor updates. Use it with caution.',
// Original text: "Connect PIF"
connectPif: 'Connect PIF',

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