Compare commits
83 Commits
xo-server-
...
select-nfs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2951f617b | ||
|
|
0ea64bdca7 | ||
|
|
f06bee3737 | ||
|
|
2693598ac8 | ||
|
|
19011ad372 | ||
|
|
86eb7744a1 | ||
|
|
fe13ef6ff9 | ||
|
|
5607d34719 | ||
|
|
4501018dd6 | ||
|
|
9be9007fde | ||
|
|
2b4443f333 | ||
|
|
ab6548122f | ||
|
|
f81573d999 | ||
|
|
84ccebb858 | ||
|
|
530bc50e7c | ||
|
|
57e490fc23 | ||
|
|
61e902c094 | ||
|
|
8378ba77d6 | ||
|
|
c9e30b74e2 | ||
|
|
af944fd2e3 | ||
|
|
bcc0e76f1d | ||
|
|
95078d250a | ||
|
|
4b16a2c0c5 | ||
|
|
b8524732ce | ||
|
|
d641d35d5c | ||
|
|
7464d95b57 | ||
|
|
3d6aa667fe | ||
|
|
147c3d2e7b | ||
|
|
ac298c3be3 | ||
|
|
e88848c44a | ||
|
|
cd518e3e4c | ||
|
|
24d4fad394 | ||
|
|
6d8785e689 | ||
|
|
508cbf0a82 | ||
|
|
c83f56166d | ||
|
|
7199e1a214 | ||
|
|
cc2c71c076 | ||
|
|
9ca273b2c4 | ||
|
|
b85c2f35b6 | ||
|
|
fdd79885f9 | ||
|
|
b2eb970796 | ||
|
|
3ee9c1b550 | ||
|
|
2566c24753 | ||
|
|
49e1b0ba7e | ||
|
|
453c329f14 | ||
|
|
27193f38f3 | ||
|
|
d3dc94e210 | ||
|
|
6dad860635 | ||
|
|
0362ac8909 | ||
|
|
e7b79f83d1 | ||
|
|
62379c1e41 | ||
|
|
23b422e3df | ||
|
|
f8e6dee635 | ||
|
|
c8e9b287f4 | ||
|
|
c9412dbcd0 | ||
|
|
77222e9e6b | ||
|
|
9d0f24eae1 | ||
|
|
6e527947be | ||
|
|
e7051c1129 | ||
|
|
3196c7ca09 | ||
|
|
0e1e32d241 | ||
|
|
a34912fb0d | ||
|
|
c7c6e0e2ff | ||
|
|
1e529c995a | ||
|
|
7be1c7a47b | ||
|
|
b17380443b | ||
|
|
59e68682bd | ||
|
|
b7a92cfe92 | ||
|
|
5ebe27da49 | ||
|
|
42df6ba6fa | ||
|
|
8210fddfab | ||
|
|
f55ed273c5 | ||
|
|
d67e95af7b | ||
|
|
0b0f235252 | ||
|
|
36a5f52068 | ||
|
|
31266728f7 | ||
|
|
8c79ea4ce3 | ||
|
|
c73a4204cb | ||
|
|
0b3c2cc252 | ||
|
|
2bd3ca1d0b | ||
|
|
ce8649d991 | ||
|
|
9bd563b111 | ||
|
|
6ceb924a85 |
@@ -35,6 +35,9 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// disabled because XAPI objects are using camel case
|
||||
camelcase: ['off'],
|
||||
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
'no-var': 'error',
|
||||
'node/no-extraneous-import': 'error',
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"golike-defer": "^0.4.1",
|
||||
"xen-api": "^0.25.1"
|
||||
"xen-api": "^0.25.2"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish"
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -350,5 +350,30 @@ handlers.forEach(url => {
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('#truncate()', () => {
|
||||
forOwn(
|
||||
{
|
||||
'shrinks file': (() => {
|
||||
const length = random(0, TEST_DATA_LEN)
|
||||
const expected = TEST_DATA.slice(0, length)
|
||||
return { length, expected }
|
||||
})(),
|
||||
'grows file': (() => {
|
||||
const length = random(TEST_DATA_LEN, TEST_DATA_LEN * 2)
|
||||
const expected = Buffer.alloc(length)
|
||||
TEST_DATA.copy(expected)
|
||||
return { length, expected }
|
||||
})(),
|
||||
},
|
||||
({ length, expected }, title) => {
|
||||
it(title, async () => {
|
||||
await handler.outputFile('file', TEST_DATA)
|
||||
await handler.truncate('file', length)
|
||||
await expect(await handler.readFile('file')).toEqual(expected)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -106,6 +106,10 @@ export default class LocalHandler extends RemoteHandlerAbstract {
|
||||
await fs.access(path, fs.R_OK | fs.W_OK)
|
||||
}
|
||||
|
||||
_truncate(file, len) {
|
||||
return fs.truncate(this._getFilePath(file), len)
|
||||
}
|
||||
|
||||
async _unlink(file) {
|
||||
return fs.unlink(this._getFilePath(file))
|
||||
}
|
||||
|
||||
@@ -155,6 +155,12 @@ export default class SmbHandler extends RemoteHandlerAbstract {
|
||||
return this.list('.')
|
||||
}
|
||||
|
||||
_truncate(file, len) {
|
||||
return this._client
|
||||
.truncate(this._getFilePath(file), len)
|
||||
.catch(normalizeError)
|
||||
}
|
||||
|
||||
_unlink(file) {
|
||||
return this._client.unlink(this._getFilePath(file)).catch(normalizeError)
|
||||
}
|
||||
|
||||
53
CHANGELOG.md
53
CHANGELOG.md
@@ -1,7 +1,60 @@
|
||||
# ChangeLog
|
||||
|
||||
## **next**
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [VM/Advanced] Ability to use UEFI instead of BIOS [#4264](https://github.com/vatesfr/xen-orchestra/issues/4264) (PR [#4268](https://github.com/vatesfr/xen-orchestra/pull/4268))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [XOA] Don't require editing the _email_ field in case of re-registration (PR [#4259](https://github.com/vatesfr/xen-orchestra/pull/4259))
|
||||
|
||||
### Released packages
|
||||
|
||||
- xen-api v0.25.2
|
||||
- xo-server v5.43.0
|
||||
- xo-web v5.43.0
|
||||
|
||||
## **5.35.0** (2019-05-29)
|
||||
|
||||

|
||||
|
||||
### Enhancements
|
||||
|
||||
- [VM/general] Display 'Started... ago' instead of 'Halted... ago' for paused state [#3750](https://github.com/vatesfr/xen-orchestra/issues/3750) (PR [#4170](https://github.com/vatesfr/xen-orchestra/pull/4170))
|
||||
- [Metadata backup] Ability to define when the backup report will be sent (PR [#4149](https://github.com/vatesfr/xen-orchestra/pull/4149))
|
||||
- [XOA/Update] Ability to select release channel [#4200](https://github.com/vatesfr/xen-orchestra/issues/4200) (PR [#4202](https://github.com/vatesfr/xen-orchestra/pull/4202))
|
||||
- [User] Forget connection tokens on password change or on demand [#4214](https://github.com/vatesfr/xen-orchestra/issues/4214) (PR [#4224](https://github.com/vatesfr/xen-orchestra/pull/4224))
|
||||
- [Settings/Logs] LICENCE_RESTRICTION errors: suggest XCP-ng as an Open Source alternative [#3876](https://github.com/vatesfr/xen-orchestra/issues/3876) (PR [#4238](https://github.com/vatesfr/xen-orchestra/pull/4238))
|
||||
- [VM/Migrate] Display VDI size on migrate modal [#2534](https://github.com/vatesfr/xen-orchestra/issues/2534) (PR [#4250](https://github.com/vatesfr/xen-orchestra/pull/4250))
|
||||
- [Host] Display hyperthreading status on advanced tab [#4262](https://github.com/vatesfr/xen-orchestra/issues/4262) (PR [#4263](https://github.com/vatesfr/xen-orchestra/pull/4263))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [Pool/Patches] Fix "an error has occurred" in "Applied patches" [#4192](https://github.com/vatesfr/xen-orchestra/issues/4192) (PR [#4193](https://github.com/vatesfr/xen-orchestra/pull/4193))
|
||||
- [Backup NG] Fix report sent even though "Never" is selected [#4092](https://github.com/vatesfr/xen-orchestra/issues/4092) (PR [#4178](https://github.com/vatesfr/xen-orchestra/pull/4178))
|
||||
- [Remotes] Fix issues after a config import (PR [#4197](https://github.com/vatesfr/xen-orchestra/pull/4197))
|
||||
- [Charts] Fixed the chart lines sometimes changing order/color (PR [#4221](https://github.com/vatesfr/xen-orchestra/pull/4221))
|
||||
- Prevent non-admin users to access admin pages with URL (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.1
|
||||
- @xen-orchestra/fs v0.9.0
|
||||
- vhd-lib v0.7.0
|
||||
- xo-server v5.42.1
|
||||
- xo-web v5.42.1
|
||||
|
||||
## **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))
|
||||
|
||||
@@ -2,16 +2,12 @@
|
||||
|
||||
### Enhancements
|
||||
|
||||
### Bug fixes
|
||||
- [Backup-ng/restore] Display size for full VM backup [#4009](https://github.com/vatesfr/xen-orchestra/issues/4009) (PR [#4245](https://github.com/vatesfr/xen-orchestra/pull/4245))
|
||||
- [Sr/new] Ability to select NFS version when creating NFS storage [#3951](https://github.com/vatesfr/xen-orchestra/issues/#3951) (PR [#4277](https://github.com/vatesfr/xen-orchestra/pull/4277))
|
||||
|
||||
- [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))
|
||||
### Bug fixes
|
||||
|
||||
### Released packages
|
||||
|
||||
- xo-server-backup-reports v0.16.1
|
||||
- @xen-orchestra/fs v0.9.0
|
||||
- vhd-lib v0.7.0
|
||||
- xo-server v5.41.0
|
||||
- xo-web v5.41.0
|
||||
- xo-server v5.44.0
|
||||
- xo-web v5.44.0
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
"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.98.0",
|
||||
"flow-bin": "^0.100.0",
|
||||
"globby": "^9.0.0",
|
||||
"husky": "^2.2.0",
|
||||
"jest": "^24.1.0",
|
||||
|
||||
@@ -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",
|
||||
@@ -40,7 +40,7 @@
|
||||
"cross-env": "^5.1.3",
|
||||
"execa": "^1.0.0",
|
||||
"fs-promise": "^2.0.0",
|
||||
"get-stream": "^4.0.0",
|
||||
"get-stream": "^5.1.0",
|
||||
"index-modules": "^0.3.0",
|
||||
"readable-stream": "^3.0.6",
|
||||
"rimraf": "^2.6.2",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"human-format": "^0.10.0",
|
||||
"lodash": "^4.17.4",
|
||||
"pw": "^0.0.4",
|
||||
"xen-api": "^0.25.1"
|
||||
"xen-api": "^0.25.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.1.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xen-api",
|
||||
"version": "0.25.1",
|
||||
"version": "0.25.2",
|
||||
"license": "ISC",
|
||||
"description": "Connector to the Xen API",
|
||||
"keywords": [
|
||||
|
||||
@@ -1059,9 +1059,14 @@ export class Xapi extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
props[`add_to_${field}`] = function(...values) {
|
||||
props[`add_${field}`] = function(value) {
|
||||
return xapi
|
||||
.call(`${type}.add_${field}`, this.$ref, values)
|
||||
.call(`${type}.add_${field}`, this.$ref, value)
|
||||
.then(noop)
|
||||
}
|
||||
props[`remove_${field}`] = function(value) {
|
||||
return xapi
|
||||
.call(`${type}.remove_${field}`, this.$ref, value)
|
||||
.then(noop)
|
||||
}
|
||||
} else if (value !== null && typeof value === 'object') {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -49,6 +49,11 @@ maxTokenValidity = '0.5 year'
|
||||
# Delay for which backups listing on a remote is cached
|
||||
listingDebounce = '1 min'
|
||||
|
||||
# Duration for which we can wait for the backup size before returning
|
||||
#
|
||||
# It should be short to avoid blocking the display of the available backups.
|
||||
vmBackupSizeTimeout = '2 seconds'
|
||||
|
||||
# Helmet handles HTTP security via headers
|
||||
#
|
||||
# https://helmetjs.github.io/docs/
|
||||
@@ -74,6 +79,7 @@ honorCipherOrder = true
|
||||
secureOptions = 117440512
|
||||
|
||||
[http.mounts]
|
||||
'/' = '../xo-web/dist'
|
||||
|
||||
[remoteOptions]
|
||||
mountsDir = '/run/xo-server/mounts'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "xo-server",
|
||||
"version": "5.41.0",
|
||||
"version": "5.43.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Server part of Xen-Orchestra",
|
||||
"keywords": [
|
||||
@@ -42,7 +42,7 @@
|
||||
"@xen-orchestra/log": "^0.1.4",
|
||||
"@xen-orchestra/mixin": "^0.0.0",
|
||||
"ajv": "^6.1.1",
|
||||
"app-conf": "^0.6.1",
|
||||
"app-conf": "^0.7.0",
|
||||
"archiver": "^3.0.0",
|
||||
"async-iterator-to-stream": "^1.0.1",
|
||||
"base64url": "^3.0.0",
|
||||
@@ -51,7 +51,7 @@
|
||||
"body-parser": "^1.18.2",
|
||||
"compression": "^1.7.3",
|
||||
"connect-flash": "^0.1.1",
|
||||
"cookie": "^0.3.1",
|
||||
"cookie": "^0.4.0",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"d3-time-format": "^2.1.1",
|
||||
"debug": "^4.0.1",
|
||||
@@ -65,7 +65,7 @@
|
||||
"express-session": "^1.15.6",
|
||||
"fatfs": "^0.10.4",
|
||||
"from2": "^2.3.0",
|
||||
"fs-extra": "^7.0.0",
|
||||
"fs-extra": "^8.0.1",
|
||||
"get-stream": "^4.0.0",
|
||||
"golike-defer": "^0.4.1",
|
||||
"hashy": "^0.7.1",
|
||||
@@ -123,7 +123,7 @@
|
||||
"value-matcher": "^0.2.0",
|
||||
"vhd-lib": "^0.7.0",
|
||||
"ws": "^6.0.0",
|
||||
"xen-api": "^0.25.1",
|
||||
"xen-api": "^0.25.2",
|
||||
"xml2js": "^0.4.19",
|
||||
"xo-acl-resolver": "^0.4.1",
|
||||
"xo-collection": "^0.4.1",
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -183,6 +183,7 @@ getLogs.params = {
|
||||
after: { type: ['number', 'string'], optional: true },
|
||||
before: { type: ['number', 'string'], optional: true },
|
||||
limit: { type: 'number', optional: true },
|
||||
'*': { type: 'any' },
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -4,23 +4,19 @@ 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'
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -5,14 +5,15 @@ 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 = {
|
||||
|
||||
@@ -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 = {
|
||||
@@ -179,6 +180,35 @@ createIso.resolve = {
|
||||
host: ['host', 'host', 'administrate'],
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export async function createFile({
|
||||
host,
|
||||
nameLabel,
|
||||
nameDescription,
|
||||
location,
|
||||
}) {
|
||||
const xapi = this.getXapi(host)
|
||||
return xapi.createSr({
|
||||
hostRef: host._xapiRef,
|
||||
name_label: nameLabel,
|
||||
name_description: nameDescription,
|
||||
type: 'file',
|
||||
device_config: { location },
|
||||
})
|
||||
}
|
||||
|
||||
createFile.params = {
|
||||
host: { type: 'string' },
|
||||
nameLabel: { type: 'string' },
|
||||
nameDescription: { type: 'string' },
|
||||
location: { type: 'string' },
|
||||
}
|
||||
|
||||
createFile.resolve = {
|
||||
host: ['host', 'host', 'administrate'],
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// NFS SR
|
||||
|
||||
@@ -361,6 +391,58 @@ createExt.resolve = {
|
||||
host: ['host', 'host', 'administrate'],
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// This function helps to detect all ZFS pools
|
||||
// Return a dict of pools with their parameters { <poolname>: {<paramdict>}}
|
||||
// example output (the parameter mountpoint is of interest):
|
||||
// {"tank":
|
||||
// {
|
||||
// "setuid": "on", "relatime": "off", "referenced": "24K", "written": "24K", "zoned": "off", "primarycache": "all",
|
||||
// "logbias": "latency", "creation": "Mon May 27 17:24 2019", "sync": "standard", "snapdev": "hidden",
|
||||
// "dedup": "off", "sharenfs": "off", "usedbyrefreservation": "0B", "sharesmb": "off", "createtxg": "1",
|
||||
// "canmount": "on", "mountpoint": "/tank", "casesensitivity": "sensitive", "utf8only": "off", "xattr": "on",
|
||||
// "dnodesize": "legacy", "mlslabel": "none", "objsetid": "54", "defcontext": "none", "rootcontext": "none",
|
||||
// "mounted": "yes", "compression": "off", "overlay": "off", "logicalused": "47K", "usedbysnapshots": "0B",
|
||||
// "filesystem_count": "none", "copies": "1", "snapshot_limit": "none", "aclinherit": "restricted",
|
||||
// "compressratio": "1.00x", "readonly": "off", "version": "5", "normalization": "none", "filesystem_limit": "none",
|
||||
// "type": "filesystem", "secondarycache": "all", "refreservation": "none", "available": "17.4G", "used": "129K",
|
||||
// "exec": "on", "refquota": "none", "refcompressratio": "1.00x", "quota": "none", "keylocation": "none",
|
||||
// "snapshot_count": "none", "fscontext": "none", "vscan": "off", "reservation": "none", "atime": "on",
|
||||
// "recordsize": "128K", "usedbychildren": "105K", "usedbydataset": "24K", "guid": "656061077639704004",
|
||||
// "pbkdf2iters": "0", "checksum": "on", "special_small_blocks": "0", "redundant_metadata": "all",
|
||||
// "volmode": "default", "devices": "on", "keyformat": "none", "logicalreferenced": "12K", "acltype": "off",
|
||||
// "nbmand": "off", "context": "none", "encryption": "off", "snapdir": "hidden"}}
|
||||
export async function probeZfs({ host }) {
|
||||
const xapi = this.getXapi(host)
|
||||
try {
|
||||
const result = await xapi.call(
|
||||
'host.call_plugin',
|
||||
host._xapiRef,
|
||||
'zfs.py',
|
||||
'list_zfs_pools',
|
||||
{}
|
||||
)
|
||||
return JSON.parse(result)
|
||||
} catch (error) {
|
||||
if (
|
||||
error.code === 'XENAPI_MISSING_PLUGIN' ||
|
||||
error.code === 'UNKNOWN_XENAPI_PLUGIN_FUNCTION'
|
||||
) {
|
||||
return {}
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
probeZfs.params = {
|
||||
host: { type: 'string' },
|
||||
}
|
||||
|
||||
probeZfs.resolve = {
|
||||
host: ['host', 'host', 'administrate'],
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// This function helps to detect all NFS shares (exports) on a NFS server
|
||||
// Return a table of exports with their paths and ACLs
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export async function add({ tag, object }) {
|
||||
await this.getXapi(object).addTag(object._xapiId, tag)
|
||||
await this.getXapiObject(object).add_tags(tag)
|
||||
}
|
||||
|
||||
add.description = 'add a new tag to an object'
|
||||
@@ -16,7 +16,7 @@ add.params = {
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export async function remove({ tag, object }) {
|
||||
await this.getXapi(object).removeTag(object._xapiId, tag)
|
||||
await this.getXapiObject(object).remove_tags(tag)
|
||||
}
|
||||
|
||||
remove.description = 'remove an existing tag from an object'
|
||||
|
||||
@@ -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 },
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -320,6 +320,11 @@ create.params = {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
hvmBootFirmware: { type: 'string', optional: true },
|
||||
|
||||
// other params are passed to `editVm`
|
||||
'*': { type: 'any' },
|
||||
}
|
||||
|
||||
create.resolve = {
|
||||
@@ -603,7 +608,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 },
|
||||
|
||||
@@ -621,6 +626,9 @@ set.params = {
|
||||
|
||||
// set the VM network interface controller
|
||||
nicType: { type: ['string', 'null'], optional: true },
|
||||
|
||||
// set the VM boot firmware mode
|
||||
hvmBootFirmware: { type: ['string', 'null'], optional: true },
|
||||
}
|
||||
|
||||
set.resolve = {
|
||||
@@ -630,13 +638,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 +739,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 +1091,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 +1105,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 = {
|
||||
@@ -1366,9 +1368,7 @@ createInterface.resolve = {
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export async function attachPci({ vm, pciId }) {
|
||||
const xapi = this.getXapi(vm)
|
||||
|
||||
await xapi.call('VM.add_to_other_config', vm._xapiRef, 'pci', pciId)
|
||||
await this.getXapiObject(vm).update_other_config('pci', pciId)
|
||||
}
|
||||
|
||||
attachPci.params = {
|
||||
@@ -1383,9 +1383,7 @@ attachPci.resolve = {
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export async function detachPci({ vm }) {
|
||||
const xapi = this.getXapi(vm)
|
||||
|
||||
await xapi.call('VM.remove_from_other_config', vm._xapiRef, 'pci')
|
||||
await this.getXapiObject(vm).update_other_config('pci', null)
|
||||
}
|
||||
|
||||
detachPci.params = {
|
||||
@@ -1418,15 +1416,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).update_HVM_boot_params('order', order)
|
||||
}
|
||||
|
||||
setBootOrder.params = {
|
||||
|
||||
@@ -55,6 +55,7 @@ getAllObjects.description = 'Returns all XO objects'
|
||||
getAllObjects.params = {
|
||||
filter: { type: 'object', optional: true },
|
||||
limit: { type: 'number', optional: true },
|
||||
ndjson: { type: 'boolean', optional: true },
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
@@ -1124,7 +1124,7 @@ async function _prepareGlusterVm(
|
||||
}
|
||||
}
|
||||
}
|
||||
await xapi.addTag(newVM.$id, 'XOSAN')
|
||||
await newVM.add_tags('XOSAN')
|
||||
await xapi.editVm(newVM, {
|
||||
name_label: `XOSAN - ${lvmSr.name_label} - ${
|
||||
host.name_label
|
||||
@@ -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'
|
||||
|
||||
@@ -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'),
|
||||
}
|
||||
|
||||
@@ -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,101 +247,10 @@ 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) {
|
||||
const { $ref: ref, $type: type } = this.getObject(id)
|
||||
|
||||
await this.call(`${type}.add_tags`, ref, tag)
|
||||
}
|
||||
|
||||
async removeTag(id, tag) {
|
||||
const { $ref: ref, $type: type } = this.getObject(id)
|
||||
|
||||
await this.call(`${type}.remove_tags`, ref, tag)
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
||||
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 +279,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 +297,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 +312,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 +347,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 +360,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 +386,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 +400,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 +471,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 +611,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 +625,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 =>
|
||||
@@ -1065,23 +956,21 @@ export default class Xapi extends XapiBase {
|
||||
await this._createVmRecord({
|
||||
...delta.vm,
|
||||
affinity: null,
|
||||
blocked_operations: {
|
||||
...delta.vm.blocked_operations,
|
||||
start: 'Importing…',
|
||||
},
|
||||
ha_always_run: false,
|
||||
is_a_template: false,
|
||||
name_label: `[Importing…] ${name_label}`,
|
||||
other_config: {
|
||||
...delta.vm.other_config,
|
||||
[TAG_COPY_SRC]: delta.vm.uuid,
|
||||
},
|
||||
})
|
||||
)
|
||||
$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,
|
||||
}),
|
||||
])
|
||||
|
||||
// 2. Delete all VBDs which may have been created by the import.
|
||||
await asyncMap(vm.$VBDs, vbd => this._deleteVbd(vbd))::ignoreErrors()
|
||||
|
||||
@@ -1103,9 +992,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 +1087,15 @@ export default class Xapi extends XapiBase {
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
this._setObjectProperties(vm, {
|
||||
name_label,
|
||||
}),
|
||||
delta.vm.ha_always_run && vm.set_ha_always_run(true),
|
||||
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 +1149,7 @@ export default class Xapi extends XapiBase {
|
||||
)
|
||||
|
||||
const loop = () =>
|
||||
this.call(
|
||||
this.callAsync(
|
||||
'VM.migrate_send',
|
||||
vm.$ref,
|
||||
token,
|
||||
@@ -1276,7 +1163,7 @@ export default class Xapi extends XapiBase {
|
||||
pDelay(1e4).then(loop)
|
||||
)
|
||||
|
||||
return loop()
|
||||
return loop().then(noop)
|
||||
}
|
||||
|
||||
@synchronized()
|
||||
@@ -1431,14 +1318,8 @@ export default class Xapi extends XapiBase {
|
||||
$defer.onFailure(() => this._deleteVm(vm))
|
||||
// Disable start and change the VM name label during import.
|
||||
await Promise.all([
|
||||
this.addForbiddenOperationToVm(
|
||||
vm.$id,
|
||||
'start',
|
||||
'OVA import in progress...'
|
||||
),
|
||||
this._setObjectProperties(vm, {
|
||||
name_label: `[Importing...] ${nameLabel}`,
|
||||
}),
|
||||
vm.update_blocked_operations('start', 'OVA import in progress...'),
|
||||
vm.set_name_label(`[Importing...] ${nameLabel}`),
|
||||
])
|
||||
|
||||
// 2. Create VDIs & Vifs.
|
||||
@@ -1455,7 +1336,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,
|
||||
})
|
||||
@@ -1498,8 +1379,8 @@ 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.update_blocked_operations('start', null),
|
||||
vm.set_name_label(nameLabel),
|
||||
])
|
||||
return vm
|
||||
}
|
||||
@@ -1546,7 +1427,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 +1512,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 +1526,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 +1555,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 +1582,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,35 +1600,15 @@ 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()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vm_operations: http://xapi-project.github.io/xen-api/classes/vm.html
|
||||
async addForbiddenOperationToVm(vmId, operation, reason) {
|
||||
await this.call(
|
||||
'VM.add_to_blocked_operations',
|
||||
this.getObject(vmId).$ref,
|
||||
operation,
|
||||
`[XO] ${reason}`
|
||||
)
|
||||
}
|
||||
|
||||
async removeForbiddenOperationFromVm(vmId, operation) {
|
||||
await this.call(
|
||||
'VM.remove_from_blocked_operations',
|
||||
this.getObject(vmId).$ref,
|
||||
operation
|
||||
)
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
||||
async createVbd({
|
||||
@@ -1826,14 +1669,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 +1699,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 +1711,7 @@ export default class Xapi extends XapiBase {
|
||||
type,
|
||||
virtual_size: size !== undefined ? parseSize(size) : virtual_size,
|
||||
xenstore_data,
|
||||
})
|
||||
}).then(extractOpaqueRef)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1886,9 +1729,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 +1745,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 +1765,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 +1778,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 +1792,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 +1800,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 +1826,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 +1835,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 +1886,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 +2014,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 +2042,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 +2081,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 +2096,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 +2127,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 +2136,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)
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
||||
@@ -68,18 +68,12 @@ 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>
|
||||
): Promise<void>;
|
||||
_snapshotVm(cancelToken: mixed, vm: Vm, nameLabel?: string): Promise<Vm>;
|
||||
|
||||
addTag(object: Id, tag: string): Promise<void>;
|
||||
barrier(): Promise<void>;
|
||||
barrier(ref: string): Promise<XapiObject>;
|
||||
deleteVm(vm: Id): Promise<void>;
|
||||
|
||||
@@ -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)
|
||||
),
|
||||
]),
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
@@ -84,4 +84,32 @@ export default {
|
||||
})
|
||||
return unhealthyVdis
|
||||
},
|
||||
|
||||
async createSr({
|
||||
hostRef,
|
||||
|
||||
content_type = 'user', // recommended by Citrix
|
||||
device_config = {},
|
||||
name_description = '',
|
||||
name_label,
|
||||
shared = false,
|
||||
physical_size = 0,
|
||||
sm_config = {},
|
||||
type,
|
||||
}) {
|
||||
const srRef = await this.call(
|
||||
'SR.create',
|
||||
hostRef,
|
||||
device_config,
|
||||
physical_size,
|
||||
name_label,
|
||||
name_description,
|
||||
type,
|
||||
content_type,
|
||||
shared,
|
||||
sm_config
|
||||
)
|
||||
|
||||
return (await this.barrier(srRef)).uuid
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -107,17 +107,12 @@ export default {
|
||||
|
||||
if (isHvm) {
|
||||
if (!isEmpty(vdis) || installMethod === 'network') {
|
||||
const { HVM_boot_params: bootParams } = vm
|
||||
let order = bootParams.order
|
||||
if (order) {
|
||||
order = 'n' + order.replace('n', '')
|
||||
} else {
|
||||
order = 'ncd'
|
||||
}
|
||||
const { order } = vm.HVM_boot_params
|
||||
|
||||
this._setObjectProperties(vm, {
|
||||
HVM_boot_params: { ...bootParams, order },
|
||||
})
|
||||
vm.update_HVM_boot_params(
|
||||
'order',
|
||||
order ? 'n' + order.replace('n', '') : 'ncd'
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// PV
|
||||
@@ -125,13 +120,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 +258,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 +276,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 +306,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 +335,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 +415,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 +430,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 +441,17 @@ 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),
|
||||
},
|
||||
|
||||
hvmBootFirmware: {
|
||||
set: (firmware, vm) => vm.update_HVM_boot_params('firmware', firmware),
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -493,7 +464,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 +477,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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -60,8 +60,9 @@ function checkParams(method, params) {
|
||||
|
||||
const result = schemaInspector.validate(
|
||||
{
|
||||
type: 'object',
|
||||
properties: schema,
|
||||
strict: true,
|
||||
type: 'object',
|
||||
},
|
||||
params
|
||||
)
|
||||
@@ -261,11 +262,15 @@ export default class Api {
|
||||
//
|
||||
// The goal here is to standardize the calls by always providing
|
||||
// an id parameter when possible to simplify calls to the API.
|
||||
if (params != null && params.id === undefined) {
|
||||
if (params?.id === undefined) {
|
||||
const namespace = name.slice(0, name.indexOf('.'))
|
||||
const id = params[namespace]
|
||||
if (typeof id === 'string') {
|
||||
params.id = id
|
||||
const spec = method.params
|
||||
if (spec !== undefined && 'id' in spec && !(namespace in spec)) {
|
||||
const id = params[namespace]
|
||||
if (typeof id === 'string') {
|
||||
delete params[namespace]
|
||||
params.id = id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
ignoreErrors,
|
||||
pFinally,
|
||||
pFromEvent,
|
||||
timeout,
|
||||
} from 'promise-toolbox'
|
||||
import Vhd, {
|
||||
chainVhd,
|
||||
@@ -41,6 +42,7 @@ import { type CallJob, type Executor, type Job } from '../jobs'
|
||||
import { type Schedule } from '../scheduling'
|
||||
|
||||
import createSizeStream from '../../size-stream'
|
||||
import parseDuration from '../../_parseDuration'
|
||||
import {
|
||||
type DeltaVmExport,
|
||||
type DeltaVmImport,
|
||||
@@ -286,7 +288,7 @@ const importers: $Dict<
|
||||
xapi.importVm(xva, { srId: sr.$id })
|
||||
)
|
||||
await Promise.all([
|
||||
xapi.addTag(vm.$id, 'restored from backup'),
|
||||
vm.add_tags('restored from backup'),
|
||||
xapi.editVm(vm.$id, {
|
||||
name_label: `${metadata.vm.name_label} (${safeDateFormat(
|
||||
metadata.timestamp
|
||||
@@ -449,10 +451,8 @@ const disableVmHighAvailability = async (xapi: Xapi, vm: Vm) => {
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
xapi._setObjectProperties(vm, {
|
||||
haRestartPriority: '',
|
||||
}),
|
||||
xapi.addTag(vm.$ref, 'HA disabled'),
|
||||
vm.set_ha_restart_priority(''),
|
||||
vm.add_tags('HA disabled'),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -509,9 +509,17 @@ const disableVmHighAvailability = async (xapi: Xapi, vm: Vm) => {
|
||||
// │ │ ├─ task.start(message: 'transfer')
|
||||
// │ │ │ ├─ task.warning(message: string)
|
||||
// │ │ │ └─ task.end(result: { size: number })
|
||||
// │ │ │
|
||||
// │ │ │ // in case of full backup, DR and CR
|
||||
// │ │ ├─ task.start(message: 'clean')
|
||||
// │ │ │ ├─ task.warning(message: string)
|
||||
// │ │ │ └─ task.end
|
||||
// │ │ │
|
||||
// │ │ │ // in case of delta backup
|
||||
// │ │ ├─ task.start(message: 'merge')
|
||||
// │ │ │ ├─ task.warning(message: string)
|
||||
// │ │ │ └─ task.end(result: { size: number })
|
||||
// │ │ │
|
||||
// │ │ └─ task.end
|
||||
// │ └─ task.end
|
||||
// └─ job.end
|
||||
@@ -538,10 +546,11 @@ export default class BackupNg {
|
||||
return this._runningRestores
|
||||
}
|
||||
|
||||
constructor(app: any) {
|
||||
constructor(app: any, { backup }) {
|
||||
this._app = app
|
||||
this._logger = undefined
|
||||
this._runningRestores = new Set()
|
||||
this._backupOptions = backup
|
||||
|
||||
app.on('start', async () => {
|
||||
this._logger = await app.getLogger('restore')
|
||||
@@ -937,7 +946,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 +1060,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,
|
||||
@@ -1193,11 +1202,20 @@ export default class BackupNg {
|
||||
)
|
||||
): any)
|
||||
|
||||
const deleteOldBackups = () =>
|
||||
wrapTask(
|
||||
{
|
||||
logger,
|
||||
message: 'clean',
|
||||
parentId: taskId,
|
||||
},
|
||||
this._deleteFullVmBackups(handler, oldBackups)
|
||||
)
|
||||
const deleteFirst = getSetting(settings, 'deleteFirst', [
|
||||
remoteId,
|
||||
])
|
||||
if (deleteFirst) {
|
||||
await this._deleteFullVmBackups(handler, oldBackups)
|
||||
await deleteOldBackups()
|
||||
}
|
||||
|
||||
await wrapTask(
|
||||
@@ -1213,7 +1231,7 @@ export default class BackupNg {
|
||||
await handler.outputFile(metadataFilename, jsonMetadata)
|
||||
|
||||
if (!deleteFirst) {
|
||||
await this._deleteFullVmBackups(handler, oldBackups)
|
||||
await deleteOldBackups()
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -1244,9 +1262,18 @@ export default class BackupNg {
|
||||
listReplicatedVms(xapi, scheduleId, srId, vmUuid)
|
||||
)
|
||||
|
||||
const deleteOldBackups = () =>
|
||||
wrapTask(
|
||||
{
|
||||
logger,
|
||||
message: 'clean',
|
||||
parentId: taskId,
|
||||
},
|
||||
this._deleteVms(xapi, oldVms)
|
||||
)
|
||||
const deleteFirst = getSetting(settings, 'deleteFirst', [srId])
|
||||
if (deleteFirst) {
|
||||
await this._deleteVms(xapi, oldVms)
|
||||
await deleteOldBackups()
|
||||
}
|
||||
|
||||
const vm = await xapi.barrier(
|
||||
@@ -1258,29 +1285,27 @@ 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)})`
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
await Promise.all([
|
||||
xapi.addTag(vm.$ref, 'Disaster Recovery'),
|
||||
vm.add_tags('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) {
|
||||
await this._deleteVms(xapi, oldVms)
|
||||
await deleteOldBackups()
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -1606,9 +1631,19 @@ export default class BackupNg {
|
||||
listReplicatedVms(xapi, scheduleId, srId, vmUuid)
|
||||
)
|
||||
|
||||
const deleteOldBackups = () =>
|
||||
wrapTask(
|
||||
{
|
||||
logger,
|
||||
message: 'clean',
|
||||
parentId: taskId,
|
||||
},
|
||||
this._deleteVms(xapi, oldVms)
|
||||
)
|
||||
|
||||
const deleteFirst = getSetting(settings, 'deleteFirst', [srId])
|
||||
if (deleteFirst) {
|
||||
await this._deleteVms(xapi, oldVms)
|
||||
await deleteOldBackups()
|
||||
}
|
||||
|
||||
const { vm } = await wrapTask(
|
||||
@@ -1628,19 +1663,17 @@ export default class BackupNg {
|
||||
)
|
||||
|
||||
await Promise.all([
|
||||
xapi.addTag(vm.$ref, 'Continuous Replication'),
|
||||
vm.add_tags('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) {
|
||||
await this._deleteVms(xapi, oldVms)
|
||||
await deleteOldBackups()
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -1667,9 +1700,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')
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1769,6 +1800,16 @@ export default class BackupNg {
|
||||
const path = `${dir}/${file}`
|
||||
try {
|
||||
const metadata = JSON.parse(String(await handler.readFile(path)))
|
||||
if (metadata.mode === 'full') {
|
||||
metadata.size = await timeout
|
||||
.call(
|
||||
handler.getSize(resolveRelativeFromFile(path, metadata.xva)),
|
||||
parseDuration(this._backupOptions.vmBackupSizeTimeout)
|
||||
)
|
||||
.catch(err => {
|
||||
log.warn(`_listVmBackups, getSize`, { err })
|
||||
})
|
||||
}
|
||||
if (predicate === undefined || predicate(metadata)) {
|
||||
Object.defineProperty(metadata, '_filename', {
|
||||
value: path,
|
||||
|
||||
@@ -372,7 +372,7 @@ export default class {
|
||||
|
||||
const { datetime } = parseVmBackupPath(file)
|
||||
await Promise.all([
|
||||
xapi.addTag(vm.$id, 'restored from backup'),
|
||||
vm.add_tags('restored from backup'),
|
||||
xapi.editVm(vm.$id, {
|
||||
name_label: `${vm.name_label} (${shortDate(datetime * 1e3)})`,
|
||||
}),
|
||||
@@ -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,12 +972,13 @@ 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.',
|
||||
})
|
||||
|
||||
await targetXapi.addTag(data.vm.$id, 'Disaster Recovery')
|
||||
await Promise.all([
|
||||
data.vm.add_tags('Disaster Recovery'),
|
||||
data.vm.update_blocked_operations(
|
||||
'start',
|
||||
'Start operation for this vm is blocked, clone it if you want to use it.'
|
||||
),
|
||||
])
|
||||
|
||||
if (!deleteOldBackupsFirst) {
|
||||
await this._removeVms(targetXapi, vmsToRemove)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -386,14 +386,12 @@ export default class {
|
||||
return value && JSON.parse(value)
|
||||
},
|
||||
setData: async (id, key, value) => {
|
||||
await xapi._updateObjectMapProperty(
|
||||
xapi.getObject(id),
|
||||
'other_config',
|
||||
{
|
||||
[`xo:${camelToSnakeCase(key)}`]:
|
||||
value !== null ? JSON.stringify(value) : value,
|
||||
}
|
||||
)
|
||||
await xapi
|
||||
.getObject(id)
|
||||
.update_other_config(
|
||||
`xo:${camelToSnakeCase(key)}`,
|
||||
value !== null ? JSON.stringify(value) : value
|
||||
)
|
||||
|
||||
// Register the updated object.
|
||||
addObject(await xapi._waitObject(id))
|
||||
@@ -457,6 +455,11 @@ export default class {
|
||||
return xapi
|
||||
}
|
||||
|
||||
// returns the XAPI object corresponding to an XO object
|
||||
getXapiObject(xoObject) {
|
||||
return this.getXapi(xoObject).getObjectByRef(xoObject._xapiRef)
|
||||
}
|
||||
|
||||
_getXenServerStatus(id) {
|
||||
const xapi = this._xapis[id]
|
||||
return xapi === undefined
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
"cross-env": "^5.1.3",
|
||||
"event-to-promise": "^0.8.0",
|
||||
"execa": "^1.0.0",
|
||||
"fs-extra": "^7.0.0",
|
||||
"get-stream": "^4.0.0",
|
||||
"fs-extra": "^8.0.1",
|
||||
"get-stream": "^5.1.0",
|
||||
"index-modules": "^0.3.0",
|
||||
"rimraf": "^2.6.2"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "xo-web",
|
||||
"version": "5.40.1",
|
||||
"version": "5.43.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",
|
||||
|
||||
@@ -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 !',
|
||||
|
||||
@@ -11,8 +11,8 @@ const messages = {
|
||||
statusDisconnected: 'Disconnected',
|
||||
statusLoading: 'Loading…',
|
||||
errorPageNotFound: 'Page not found',
|
||||
errorNoSuchItem: 'no such item',
|
||||
errorUnknownItem: 'unknown {type}',
|
||||
errorNoSuchItem: 'No such item',
|
||||
errorUnknownItem: 'Unknown {type}',
|
||||
memoryFree: '{memoryFree} RAM free',
|
||||
date: 'Date',
|
||||
notifications: 'Notifications',
|
||||
@@ -32,8 +32,8 @@ const messages = {
|
||||
multipathingDisabled: 'Multipathing disabled',
|
||||
enableMultipathing: 'Enable multipathing',
|
||||
disableMultipathing: 'Disable multipathing',
|
||||
enableAllHostsMultipathing: 'Enable all hosts multipathing',
|
||||
disableAllHostsMultipathing: 'Disable all hosts multipathing',
|
||||
enableAllHostsMultipathing: 'Enable multipathing for all hosts',
|
||||
disableAllHostsMultipathing: 'Disable multipathing for all hosts',
|
||||
paths: 'Paths',
|
||||
pbdDisconnected: 'PBD disconnected',
|
||||
hasInactivePath: 'Has an inactive path',
|
||||
@@ -166,8 +166,8 @@ const messages = {
|
||||
homeHelp: 'Want some help?',
|
||||
homeAddServer: 'Add server',
|
||||
homeConnectServer: 'Connect servers',
|
||||
homeOnlineDoc: 'Online Doc',
|
||||
homeProSupport: 'Pro Support',
|
||||
homeOnlineDoc: 'Online doc',
|
||||
homeProSupport: 'Pro support',
|
||||
homeNoVms: 'There are no VMs!',
|
||||
homeNoVmsOr: 'Or…',
|
||||
homeImportVm: 'Import VM',
|
||||
@@ -206,7 +206,6 @@ const messages = {
|
||||
homeSortBySize: 'Size',
|
||||
homeSortByType: 'Type',
|
||||
homeSortByUsage: 'Usage',
|
||||
homeSortByvCPUs: 'vCPUs',
|
||||
homeSortVmsBySnapshots: 'Snapshots',
|
||||
homeSortByContainer: 'Container',
|
||||
homeSortByPool: 'Pool',
|
||||
@@ -219,9 +218,8 @@ const messages = {
|
||||
homeResourceSet: 'Resource set: {resourceSet}',
|
||||
highAvailability: 'High Availability',
|
||||
srSharedType: 'Shared {type}',
|
||||
srNotSharedType: 'Not shared {type}',
|
||||
|
||||
// ----- Home snapshots -----
|
||||
// ----- Home snapshots -----
|
||||
snapshotVmsName: 'Name',
|
||||
snapshotVmsDescription: 'Description',
|
||||
|
||||
@@ -266,14 +264,14 @@ const messages = {
|
||||
item: 'Item',
|
||||
noSelectedValue: 'No selected value',
|
||||
selectSubjects: 'Choose user(s) and/or group(s)',
|
||||
selectObjects: 'Select Object(s)…',
|
||||
selectObjects: 'Select object(s)…',
|
||||
selectRole: 'Choose a role',
|
||||
selectHosts: 'Select Host(s)…',
|
||||
selectHosts: 'Select host(s)…',
|
||||
selectHostsVms: 'Select object(s)…',
|
||||
selectNetworks: 'Select Network(s)…',
|
||||
selectNetworks: 'Select network(s)…',
|
||||
selectPifs: 'Select PIF(s)…',
|
||||
selectPools: 'Select Pool(s)…',
|
||||
selectRemotes: 'Select Remote(s)…',
|
||||
selectPools: 'Select pool(s)…',
|
||||
selectRemotes: 'Select remote(s)…',
|
||||
selectResourceSets: 'Select resource set(s)…',
|
||||
selectResourceSetsVmTemplate: 'Select template(s)…',
|
||||
selectResourceSetsSr: 'Select SR(s)…',
|
||||
@@ -290,10 +288,9 @@ const messages = {
|
||||
selectIp: 'Select IP(s)…',
|
||||
selectIpPool: 'Select IP pool(s)…',
|
||||
selectVgpuType: 'Select VGPU type(s)…',
|
||||
fillRequiredInformations: 'Fill required information.',
|
||||
fillOptionalInformations: 'Fill information (optional)',
|
||||
selectTableReset: 'Reset',
|
||||
selectCloudConfigs: 'Select Cloud Config(s)…',
|
||||
selectCloudConfigs: 'Select cloud config(s)…',
|
||||
|
||||
// --- Dates/Scheduler ---
|
||||
|
||||
@@ -307,13 +304,10 @@ const messages = {
|
||||
selectTableAllDay: 'Every day',
|
||||
selectTableAllHour: 'Every hour',
|
||||
selectTableAllMinute: 'Every minute',
|
||||
schedulingReset: 'Reset',
|
||||
unknownSchedule: 'Unknown',
|
||||
timezonePickerUseLocalTime: 'Web browser timezone',
|
||||
serverTimezoneOption: 'Server timezone ({value})',
|
||||
cronPattern: 'Cron Pattern:',
|
||||
backupEditNotFoundTitle: 'Cannot edit backup',
|
||||
backupEditNotFoundMessage: 'Missing required info for edition',
|
||||
successfulJobCall: 'Successful',
|
||||
failedJobCall: 'Failed',
|
||||
jobCallSkipped: 'Skipped',
|
||||
@@ -345,7 +339,7 @@ const messages = {
|
||||
runJob: 'Run job',
|
||||
cancelJob: 'Cancel job',
|
||||
runJobConfirm: 'Are you sure you want to run {backupType} {id} ({tag})?',
|
||||
runJobVerbose: 'One shot running started. See overview for logs.',
|
||||
runJobVerbose: 'Onetime job started. See overview for logs.',
|
||||
jobEdit: 'Edit job',
|
||||
jobDelete: 'Delete',
|
||||
jobFinished: 'Finished',
|
||||
@@ -376,7 +370,7 @@ const messages = {
|
||||
deleteSelectedJobs: 'Delete selected jobs',
|
||||
scheduleEnableAfterCreation: 'Enable immediately after creation',
|
||||
scheduleEditMessage:
|
||||
'You are editing Schedule {name} ({id}). Saving will override previous schedule state.',
|
||||
'You are editing schedule {name} ({id}). Saving will override previous schedule state.',
|
||||
jobEditMessage:
|
||||
'You are editing job {name} ({id}). Saving will override previous job state.',
|
||||
scheduleEdit: 'Edit schedule',
|
||||
@@ -390,7 +384,7 @@ const messages = {
|
||||
missingRetentions:
|
||||
'The modes need at least a schedule with retention higher than 0',
|
||||
missingExportRetention:
|
||||
'The Backup mode and The Delta Backup mode require backup retention to be higher than 0!',
|
||||
'The Backup mode and the Delta Backup mode require backup retention to be higher than 0!',
|
||||
missingCopyRetention:
|
||||
'The CR mode and The DR mode require replication retention to be higher than 0!',
|
||||
missingSnapshotRetention:
|
||||
@@ -410,25 +404,24 @@ const messages = {
|
||||
noScheduledJobs: 'No scheduled jobs.',
|
||||
legacySnapshotsLink: 'You can delete all your legacy backup snapshots.',
|
||||
newSchedule: 'New schedule',
|
||||
noJobs: 'No jobs found.',
|
||||
noSchedules: 'No schedules found',
|
||||
jobActionPlaceHolder: 'Select a xo-server API command',
|
||||
jobActionPlaceHolder: 'Select an xo-server API command',
|
||||
jobTimeoutPlaceHolder:
|
||||
'Timeout (number of seconds after which a VM is considered failed)',
|
||||
jobSchedules: 'Schedules',
|
||||
jobScheduleNamePlaceHolder: 'Name of your schedule',
|
||||
jobScheduleJobPlaceHolder: 'Select a Job',
|
||||
jobScheduleJobPlaceHolder: 'Select a job',
|
||||
jobOwnerPlaceholder: 'Job owner',
|
||||
jobUserNotFound: "This job's creator no longer exists",
|
||||
backupUserNotFound: "This backup's creator no longer exists",
|
||||
redirectToMatchingVms: 'Click here to see the matching VMs',
|
||||
migrateToBackupNg: 'Migrate to backup NG',
|
||||
migrateToBackupNg: 'Migrate to Backup NG',
|
||||
noMatchingVms: 'There are no matching VMs!',
|
||||
allMatchingVms: '{icon} See the matching VMs ({nMatchingVms, number})',
|
||||
backupOwner: 'Backup owner',
|
||||
migrateBackupSchedule: 'Migrate to backup NG',
|
||||
migrateBackupSchedule: 'Migrate to Backup NG',
|
||||
migrateBackupScheduleMessage:
|
||||
'This will migrate this backup to a backup NG. This operation is not reversible. Do you want to continue?',
|
||||
'This will convert the old backup job to a Backup NG job. This operation is not reversible. Do you want to continue?',
|
||||
runBackupNgJobConfirm: 'Are you sure you want to run {name} ({id})?',
|
||||
cancelJobConfirm: 'Are you sure you want to cancel {name} ({id})?',
|
||||
scheduleDstWarning:
|
||||
@@ -448,38 +441,38 @@ const messages = {
|
||||
smartBackup: 'Smart backup',
|
||||
snapshotRetention: 'Snapshot retention',
|
||||
backupName: 'Name',
|
||||
useDelta: 'Use delta',
|
||||
offlineSnapshot: 'Offline snapshot',
|
||||
offlineSnapshotInfo: 'Shutdown VMs before snapshotting them',
|
||||
timeout: 'Timeout',
|
||||
timeoutInfo: 'Number of hours after which a job is considered failed',
|
||||
fullBackupInterval: 'Full backup interval',
|
||||
timeoutUnit: 'in hours',
|
||||
dbAndDrRequireEnterprisePlan: 'Delta Backup and DR require Enterprise plan',
|
||||
crRequiresPremiumPlan: 'CR requires Premium plan',
|
||||
timeoutUnit: 'In hours',
|
||||
dbAndDrRequireEnterprisePlan:
|
||||
'Delta Backup and DR require an Enterprise plan',
|
||||
crRequiresPremiumPlan: 'CR requires a Premium plan',
|
||||
smartBackupModeTitle: 'Smart mode',
|
||||
backupTargetRemotes: 'Target remotes (for Export)',
|
||||
backupTargetSrs: 'Target SRs (for Replication)',
|
||||
backupTargetRemotes: 'Target remotes (for export)',
|
||||
backupTargetSrs: 'Target SRs (for replication)',
|
||||
localRemoteWarningTitle: 'Local remote selected',
|
||||
crOnThickProvisionedSrWarning:
|
||||
'Tip: using a thin-provisioned storage will consume less space. Please click on the icon to get more information',
|
||||
'Tip: Using thin-provisioned storage will consume less space. Please click on the icon to get more information',
|
||||
vmsOnThinProvisionedSrTip:
|
||||
'Tip: creating VMs on a thin-provisioned storage will consume less space when backing them up. Please click on the icon to get more information',
|
||||
'Tip: Creating VMs on thin-provisioned storage will consume less space. Please click on the icon to get more information',
|
||||
deltaBackupOnOutdatedXenServerWarning:
|
||||
'Delta Backup and Continuous Replication require at least XenServer 6.5.',
|
||||
backupNgLinkToDocumentationMessage:
|
||||
'Click for more information about the backup modes.',
|
||||
'Click for more information about the backup methods.',
|
||||
localRemoteWarningMessage:
|
||||
'Warning: local remotes will use limited XOA disk space. Only for advanced users.',
|
||||
'Warning: Local remotes will use limited XOA disk space. Only for advanced users.',
|
||||
backupVersionWarning:
|
||||
'Warning: this feature works only with XenServer 6.5 or newer.',
|
||||
'Warning: This feature works only with XenServer 6.5 or newer.',
|
||||
editBackupVmsTitle: 'VMs',
|
||||
editBackupSmartStatusTitle: 'VMs statuses',
|
||||
editBackupSmartResidentOn: 'Resident on',
|
||||
editBackupSmartNotResidentOn: 'Not resident on',
|
||||
editBackupSmartPools: 'Pools',
|
||||
editBackupSmartTags: 'Tags',
|
||||
sampleOfMatchingVms: 'Sample of matching Vms',
|
||||
sampleOfMatchingVms: 'Sample of matching VMs',
|
||||
backupReplicatedVmsInfo:
|
||||
'Replicated VMs (VMs with Continuous Replication or Disaster Recovery tag) must be excluded!',
|
||||
editBackupSmartTagsTitle: 'VMs Tags',
|
||||
@@ -495,21 +488,19 @@ const messages = {
|
||||
'Delete old backups before backing up the VMs. If the new backup fails, you will lose your old backups.',
|
||||
|
||||
// ------ New Remote -----
|
||||
remoteList: 'Remote stores for backup',
|
||||
newRemote: 'New File System Remote',
|
||||
newRemote: 'New file system remote',
|
||||
remoteTypeLocal: 'Local',
|
||||
remoteTypeNfs: 'NFS',
|
||||
remoteTypeSmb: 'SMB',
|
||||
remoteType: 'Type',
|
||||
remoteSmbWarningMessage:
|
||||
'SMB remotes are meant to work on Windows Server. For other systems (Linux Samba, which means almost all NAS), please use NFS.',
|
||||
'SMB remotes are meant to work with Windows Server. For other systems (Linux Samba, which means almost all NAS), please use NFS.',
|
||||
remoteTestTip: 'Test your remote',
|
||||
testRemote: 'Test Remote',
|
||||
testRemote: 'Test remote',
|
||||
remoteTestFailure: 'Test failed for {name}',
|
||||
remoteTestSuccess: 'Test passed for {name}',
|
||||
remoteTestError: 'Error',
|
||||
remoteTestStep: 'Test Step',
|
||||
remoteTestFile: 'Test file',
|
||||
remoteTestStep: 'Test step',
|
||||
remoteTestName: 'Test name',
|
||||
remoteTestNameFailure: 'Remote name already exists!',
|
||||
remoteTestSuccessMessage: 'The remote appears to work correctly',
|
||||
@@ -532,33 +523,29 @@ const messages = {
|
||||
remoteSpeedInfo: 'Read and write rate speed performed during latest test',
|
||||
remoteOptions: 'Options',
|
||||
remoteShare: 'Share',
|
||||
remoteAction: 'Action',
|
||||
remoteAuth: 'Auth',
|
||||
remoteMounted: 'Mounted',
|
||||
remoteUnmounted: 'Unmounted',
|
||||
remoteDeleteTip: 'Delete',
|
||||
remoteDeleteSelected: 'Delete selected remotes',
|
||||
remoteNamePlaceHolder: 'remote name *',
|
||||
remoteMyNamePlaceHolder: 'Name *',
|
||||
remoteLocalPlaceHolderPath: '/path/to/backup',
|
||||
remoteNfsPlaceHolderHost: 'host *',
|
||||
remoteNfsPlaceHolderHost: 'Host *',
|
||||
remoteNfsPlaceHolderPort: 'Port',
|
||||
remoteNfsPlaceHolderPath: 'path/to/backup',
|
||||
remoteNfsPlaceHolderOptions: 'Custom mount options. Default: vers=3',
|
||||
remoteSmbPlaceHolderRemotePath: 'subfolder [path\\\\to\\\\backup]',
|
||||
remoteSmbPlaceHolderRemotePath: 'Subfolder [path\\\\to\\\\backup]',
|
||||
remoteSmbPlaceHolderUsername: 'Username',
|
||||
remoteSmbPlaceHolderPassword: 'Password',
|
||||
remoteSmbPlaceHolderDomain: 'Domain',
|
||||
remoteSmbPlaceHolderAddressShare: '<address>\\\\<share> *',
|
||||
remoteSmbPlaceHolderOptions: 'Custom mount options',
|
||||
remotePlaceHolderPassword: 'password(fill to edit)',
|
||||
remotePlaceHolderPassword: 'Password(fill to edit)',
|
||||
|
||||
// ------ New Storage -----
|
||||
newSrTitle: 'Create a new SR',
|
||||
newSrGeneral: 'General',
|
||||
newSrTypeSelection: 'Select Storage Type:',
|
||||
newSrTypeSelection: 'Select storage type:',
|
||||
newSrSettings: 'Settings',
|
||||
newSrUsage: 'Storage Usage',
|
||||
newSrUsage: 'Storage usage',
|
||||
newSrSummary: 'Summary',
|
||||
newSrHost: 'Host',
|
||||
newSrType: 'Type',
|
||||
@@ -569,11 +556,11 @@ const messages = {
|
||||
newSrIqn: 'IQN',
|
||||
newSrLun: 'LUN',
|
||||
newSrNoHba: 'No HBA devices',
|
||||
newSrAuth: 'with auth.',
|
||||
newSrUsername: 'User Name',
|
||||
newSrAuth: 'With auth.',
|
||||
newSrUsername: 'User name',
|
||||
newSrPassword: 'Password',
|
||||
newSrDevice: 'Device',
|
||||
newSrInUse: 'in use',
|
||||
newSrInUse: 'In use',
|
||||
newSrSize: 'Size',
|
||||
newSrCreate: 'Create',
|
||||
newSrNamePlaceHolder: 'Storage name',
|
||||
@@ -584,38 +571,39 @@ const messages = {
|
||||
newSrPasswordPlaceHolder: 'Password',
|
||||
newSrLvmDevicePlaceHolder: 'Device, e.g /dev/sda…',
|
||||
newSrLocalPathPlaceHolder: '/path/to/directory',
|
||||
newSrUseNfs4: 'Use NFSv4',
|
||||
newSrNfsDefaultVersion: 'Default NFS version',
|
||||
newSrNfsOptions: 'Comma delimited NFS options',
|
||||
newSrNfs: 'NFS version',
|
||||
reattachNewSrTooltip: 'Reattach SR',
|
||||
|
||||
// ------ New Newtork -----
|
||||
createNewNetworkNoPermission: 'You have no permission to create a network',
|
||||
// ------ New Network -----
|
||||
createNewNetworkNoPermission:
|
||||
'You do not have permission to create a network',
|
||||
createNewNetworkOn: 'Create a new network on {select}',
|
||||
|
||||
// ----- Acls, Users, Groups ------
|
||||
// ----- ACLs, Users, Groups ------
|
||||
subjectName: 'Users/Groups',
|
||||
objectName: 'Object',
|
||||
aclNoneFound: 'No acls found',
|
||||
roleName: 'Role',
|
||||
aclCreate: 'Create',
|
||||
newGroupName: 'New Group Name',
|
||||
createGroup: 'Create Group',
|
||||
newGroupName: 'New group name',
|
||||
createGroup: 'Create group',
|
||||
createGroupButton: 'Create',
|
||||
deleteGroup: 'Delete Group',
|
||||
deleteGroup: 'Delete group',
|
||||
deleteGroupConfirm: 'Are you sure you want to delete this group?',
|
||||
removeUserFromGroup: 'Remove user from Group',
|
||||
removeUserFromGroup: 'Remove user from group',
|
||||
deleteUserConfirm: 'Are you sure you want to delete this user?',
|
||||
deleteUser: 'Delete user',
|
||||
deleteSelectedUsers: 'Delete selected users',
|
||||
deleteUsersModalTitle: 'Delete user{nUsers, plural, one {} other {s}}',
|
||||
deleteUsersModalMessage:
|
||||
'Are you sure you want to delete {nUsers, number} user{nUsers, plural, one {} other {s}}?',
|
||||
noUser: 'no user',
|
||||
unknownUser: 'unknown user',
|
||||
noUser: 'No user',
|
||||
unknownUser: 'Unknown user',
|
||||
noGroupFound: 'No group found',
|
||||
groupNameColumn: 'Name',
|
||||
groupUsersColumn: 'Users',
|
||||
addUserToGroupColumn: 'Add User',
|
||||
addUserToGroupColumn: 'Add user',
|
||||
userNameColumn: 'Username',
|
||||
userGroupsColumn: 'Member of',
|
||||
userCountGroups: '{nGroups, number} group{nGroups, plural, one {} other {s}}',
|
||||
@@ -629,7 +617,7 @@ const messages = {
|
||||
adminLabel: 'Admin',
|
||||
noUserInGroup: 'No user in group',
|
||||
countUsers: '{users, number} user{users, plural, one {} other {s}}',
|
||||
selectPermission: 'Select Permission',
|
||||
selectPermission: 'Select permission',
|
||||
deleteAcl: 'Delete ACL',
|
||||
deleteSelectedAcls: 'Delete selected ACLs',
|
||||
deleteAclsModalTitle: 'Delete ACL{nAcls, plural, one {} other {s}}',
|
||||
@@ -645,8 +633,7 @@ const messages = {
|
||||
unknownPluginError: 'Unknown error',
|
||||
purgePluginConfiguration: 'Purge plugin configuration',
|
||||
purgePluginConfigurationQuestion:
|
||||
'Are you sure you want to purge this configuration ?',
|
||||
editPluginConfiguration: 'Edit',
|
||||
'Are you sure you want to purge this configuration?',
|
||||
cancelPluginEdition: 'Cancel',
|
||||
pluginConfigurationSuccess: 'Plugin configuration',
|
||||
pluginConfigurationChanges: 'Plugin configuration successfully saved!',
|
||||
@@ -660,15 +647,12 @@ const messages = {
|
||||
filterName: 'Name:',
|
||||
filterValue: 'Value:',
|
||||
saveNewFilterTitle: 'Save new filter',
|
||||
setUserFiltersTitle: 'Set custom filters',
|
||||
setUserFiltersBody: 'Are you sure you want to set custom filters?',
|
||||
removeUserFilterTitle: 'Remove custom filter',
|
||||
removeUserFilterBody: 'Are you sure you want to remove custom filter?',
|
||||
removeUserFilterBody: 'Are you sure you want to remove the custom filter?',
|
||||
defaultFilter: 'Default filter',
|
||||
defaultFilters: 'Default filters',
|
||||
customFilters: 'Custom filters',
|
||||
customizeFilters: 'Customize filters',
|
||||
saveCustomFilters: 'Save custom filters',
|
||||
|
||||
// ----- VM actions ------
|
||||
startVmLabel: 'Start',
|
||||
@@ -691,7 +675,6 @@ const messages = {
|
||||
copyVmLabel: 'Copy',
|
||||
cloneVmLabel: 'Clone',
|
||||
fastCloneVmLabel: 'Fast clone',
|
||||
convertVmToTemplateLabel: 'Convert to template',
|
||||
vmConsoleLabel: 'Console',
|
||||
|
||||
// ----- SR advanced tab -----
|
||||
@@ -727,19 +710,18 @@ const messages = {
|
||||
displayAllVMs: 'Display all VMs of this pool',
|
||||
licenseRestrictions: 'License restrictions',
|
||||
licenseRestrictionsModalTitle:
|
||||
'Warning: you are using a Free XenServer license',
|
||||
actionsRestricted: 'Some actions will be restricted.',
|
||||
'Warning: You are using a Free XenServer license',
|
||||
actionsRestricted: 'Some functionality is restricted.',
|
||||
counterRestrictionsOptions: 'You can:',
|
||||
counterRestrictionsOptionsXcp:
|
||||
'upgrade to XCP-ng for free to get rid of these restrictions',
|
||||
counterRestrictionsOptionsXsLicense: 'or get a commercial Citrix license',
|
||||
// ----- Pool tabs -----
|
||||
hostsTabName: 'Hosts',
|
||||
vmsTabName: 'Vms',
|
||||
srsTabName: 'Srs',
|
||||
vmsTabName: 'VMs',
|
||||
srsTabName: 'SRs',
|
||||
// ----- Pool advanced tab -----
|
||||
poolEditAll: 'Edit all',
|
||||
poolEditRemoteSyslog: 'Edit remote syslog for all hosts',
|
||||
poolHaStatus: 'High Availability',
|
||||
poolHaEnabled: 'Enabled',
|
||||
poolHaDisabled: 'Disabled',
|
||||
@@ -750,7 +732,6 @@ const messages = {
|
||||
// ----- Pool host tab -----
|
||||
hostNameLabel: 'Name',
|
||||
hostDescription: 'Description',
|
||||
hostMemory: 'Memory',
|
||||
noHost: 'No hosts',
|
||||
memoryLeftTooltip: '{used}% used ({free} free)',
|
||||
// ----- Pool network tab -----
|
||||
@@ -767,8 +748,6 @@ const messages = {
|
||||
poolNetworkPifDetached: 'Disconnected',
|
||||
showPifs: 'Show PIFs',
|
||||
hidePifs: 'Hide PIFs',
|
||||
showDetails: 'Show details',
|
||||
hideDetails: 'Hide details',
|
||||
// ----- Pool stats tab -----
|
||||
poolNoStats: 'No stats',
|
||||
poolAllHosts: 'All hosts',
|
||||
@@ -777,9 +756,9 @@ const messages = {
|
||||
addVmLabel: 'Add VM',
|
||||
addHostLabel: 'Add Host',
|
||||
missingPatchesPool:
|
||||
'The pool needs to install {nMissingPatches, number} patch{nMissingPatches, plural, one {} other {es}}. This operation may be long.',
|
||||
'The pool needs to install {nMissingPatches, number} patch{nMissingPatches, plural, one {} other {es}}. This operation may take a while.',
|
||||
missingPatchesHost:
|
||||
'This host needs to install {nMissingPatches, number} patch{nMissingPatches, plural, one {} other {es}}. This operation may be long.',
|
||||
'This host needs to install {nMissingPatches, number} patch{nMissingPatches, plural, one {} other {es}}. This operation may take a while.',
|
||||
patchUpdateNoInstall:
|
||||
'This host cannot be added to the pool because the patches are not homogeneous.',
|
||||
addHostErrorTitle: 'Adding host failed',
|
||||
@@ -796,7 +775,7 @@ const messages = {
|
||||
rebootHostLabel: 'Reboot',
|
||||
noHostsAvailableErrorTitle: 'Error while restarting host',
|
||||
noHostsAvailableErrorMessage:
|
||||
'Some VMs cannot be migrated before restarting this host. Please try force reboot.',
|
||||
'Some VMs cannot be migrated without first rebooting this host. Please try force reboot.',
|
||||
failHostBulkRestartTitle: 'Error while restarting hosts',
|
||||
failHostBulkRestartMessage:
|
||||
'{failedHosts, number}/{totalHosts, number} host{failedHosts, plural, one {} other {s}} could not be restarted.',
|
||||
@@ -812,6 +791,7 @@ const messages = {
|
||||
memoryHostState:
|
||||
'RAM: {memoryUsed} used on {memoryTotal} ({memoryFree} free)',
|
||||
hardwareHostSettingsLabel: 'Hardware',
|
||||
hyperThreading: 'Hyper-threading (SMT)',
|
||||
hostAddress: 'Address',
|
||||
hostStatus: 'Status',
|
||||
hostBuildNumber: 'Build number',
|
||||
@@ -821,9 +801,9 @@ const messages = {
|
||||
hostMultipathingPaths:
|
||||
'{nActives, number} of {nPaths, number} path{nPaths, plural, one {} other {s}} ({ nSessions, number } iSCSI session{nSessions, plural, one {} other {s}})',
|
||||
hostMultipathingRequiredState:
|
||||
'This action will not be fulfilled if a VM is in a running state. Please ensure that all VMs are evacuated or stopped before doing this action!',
|
||||
'This action will not be fulfilled if a VM is in a running state. Please ensure that all VMs are evacuated or stopped before performing this action!',
|
||||
hostMultipathingWarning:
|
||||
'The host{nHosts, plural, one {} other {s}} will lose the connection to the SRs. Do you want to continue?',
|
||||
'The host{nHosts, plural, one {} other {s}} will lose their connection to the SRs. Do you want to continue?',
|
||||
hostXenServerVersion: 'Version',
|
||||
hostStatusEnabled: 'Enabled',
|
||||
hostStatusDisabled: 'Disabled',
|
||||
@@ -854,7 +834,6 @@ const messages = {
|
||||
'Supplemental pack successfully installed.',
|
||||
// ----- Host net tabs -----
|
||||
networkCreateButton: 'Add a network',
|
||||
networkCreateBondedButton: 'Add a bonded network',
|
||||
pifDeviceLabel: 'Device',
|
||||
pifNetworkLabel: 'Network',
|
||||
pifVlanLabel: 'VLAN',
|
||||
@@ -864,11 +843,7 @@ const messages = {
|
||||
pifMtuLabel: 'MTU',
|
||||
pifSpeedLabel: 'Speed',
|
||||
pifStatusLabel: 'Status',
|
||||
pifStatusConnected: 'Connected',
|
||||
pifStatusDisconnected: 'Disconnected',
|
||||
pifNoInterface: 'No physical interface detected',
|
||||
pifInUse: 'This interface is currently in use',
|
||||
pifAction: 'Action',
|
||||
defaultLockingMode: 'Default locking mode',
|
||||
pifConfigureIp: 'Configure IP address',
|
||||
configIpErrorTitle: 'Invalid parameters',
|
||||
@@ -879,9 +854,7 @@ const messages = {
|
||||
gateway: 'Gateway',
|
||||
// ----- Host storage tabs -----
|
||||
addSrDeviceButton: 'Add a storage',
|
||||
srNameLabel: 'Name',
|
||||
srType: 'Type',
|
||||
pbdAction: 'Action',
|
||||
pbdStatus: 'Status',
|
||||
pbdStatusConnected: 'Connected',
|
||||
pbdStatusDisconnected: 'Disconnected',
|
||||
@@ -898,13 +871,9 @@ const messages = {
|
||||
patchVersion: 'Version',
|
||||
patchApplied: 'Applied date',
|
||||
patchSize: 'Size',
|
||||
patchStatus: 'Status',
|
||||
patchStatusApplied: 'Applied',
|
||||
patchStatusNotApplied: 'Missing patches',
|
||||
patchNothing: 'No patches detected',
|
||||
patchReleaseDate: 'Release date',
|
||||
patchGuidance: 'Guidance',
|
||||
patchAction: 'Action',
|
||||
hostAppliedPatches: 'Applied patches',
|
||||
hostMissingPatches: 'Missing patches',
|
||||
hostUpToDate: 'Host up-to-date!',
|
||||
@@ -923,7 +892,6 @@ const messages = {
|
||||
changelogDate: 'Date',
|
||||
changelogDescription: 'Description',
|
||||
// ----- Pool patch tabs -----
|
||||
refreshPatches: 'Refresh patches',
|
||||
install: 'Install',
|
||||
installPatchesTitle: 'Install patch{nPatches, plural, one {} other {es}}',
|
||||
installPatchesContent:
|
||||
@@ -932,7 +900,6 @@ const messages = {
|
||||
confirmPoolPatch:
|
||||
'Are you sure you want to install all the patches on this pool?',
|
||||
poolNeedsDefaultSr: 'The pool needs a default SR to install the patches.',
|
||||
selectDefaultSr: 'Select a default SR',
|
||||
vmsHaveCds:
|
||||
'{nVms, number} VM{nVms, plural, one {} other {s}} {nVms, plural, one {has} other {have}} CDs',
|
||||
ejectCds: 'Eject CDs',
|
||||
@@ -952,10 +919,10 @@ const messages = {
|
||||
networkTabName: 'Network',
|
||||
disksTabName: 'Disk{disks, plural, one {} other {s}}',
|
||||
|
||||
powerStateHalted: 'halted',
|
||||
powerStateRunning: 'running',
|
||||
powerStateSuspended: 'suspended',
|
||||
powerStatePaused: 'paused',
|
||||
powerStateHalted: 'Halted',
|
||||
powerStateRunning: 'Running',
|
||||
powerStateSuspended: 'Suspended',
|
||||
powerStatePaused: 'Paused',
|
||||
|
||||
// ----- VM home -----
|
||||
vmCurrentStatus: 'Current status:',
|
||||
@@ -989,8 +956,8 @@ const messages = {
|
||||
copyToClipboardLabel: 'Copy',
|
||||
ctrlAltDelButtonLabel: 'Ctrl+Alt+Del',
|
||||
tipLabel: 'Tip:',
|
||||
hideHeaderTooltip: 'Hide infos',
|
||||
showHeaderTooltip: 'Show infos',
|
||||
hideHeaderTooltip: 'Hide info',
|
||||
showHeaderTooltip: 'Show info',
|
||||
|
||||
// ----- VM container tab -----
|
||||
containerName: 'Name',
|
||||
@@ -1006,14 +973,12 @@ const messages = {
|
||||
containerRestart: 'Restart this container',
|
||||
|
||||
// ----- VM disk tab -----
|
||||
vdiAction: 'Action',
|
||||
vdiAttachDeviceButton: 'Attach disk',
|
||||
vbdCreateDeviceButton: 'New disk',
|
||||
vdiBootOrder: 'Boot order',
|
||||
vdiNameLabel: 'Name',
|
||||
vdiNameDescription: 'Description',
|
||||
vdiPool: 'Pool',
|
||||
vdiDisconnect: 'Disconnect',
|
||||
vdiTags: 'Tags',
|
||||
vdiSize: 'Size',
|
||||
vdiSr: 'SR',
|
||||
@@ -1025,18 +990,16 @@ const messages = {
|
||||
vdiMigrateNoSrMessage: 'A target SR is required to migrate a VDI',
|
||||
vdiForget: 'Forget',
|
||||
vdiRemove: 'Remove VDI',
|
||||
noControlDomainVdis: 'No VDIs attached to Control Domain',
|
||||
noControlDomainVdis: 'No VDIs attached to control domain',
|
||||
vbdBootableStatus: 'Boot flag',
|
||||
vbdDevice: 'Device',
|
||||
vbdStatus: 'Status',
|
||||
vbdStatusConnected: 'Connected',
|
||||
vbdStatusDisconnected: 'Disconnected',
|
||||
vbdNoVbd: 'No disks',
|
||||
vbdConnect: 'Connect VBD',
|
||||
vbdDisconnect: 'Disconnect VBD',
|
||||
vbdBootable: 'Bootable',
|
||||
vbdReadonly: 'Readonly',
|
||||
vbdAction: 'Action',
|
||||
vbdCreate: 'Create',
|
||||
vbdAttach: 'Attach',
|
||||
vbdNamePlaceHolder: 'Disk name',
|
||||
@@ -1056,11 +1019,10 @@ const messages = {
|
||||
notEnoughSpaceInResourceSet:
|
||||
'Not enough space in resource set {resourceSet} ({spaceLeft} left)',
|
||||
warningVdiSr:
|
||||
"The VDIs' SRs must either be shared or on the same host for the VM to be able to start.",
|
||||
"The VDIs' SRs must either be shared or on the same host for the VM to be able to start.",
|
||||
|
||||
// ----- VM network tab -----
|
||||
vifCreateDeviceButton: 'New device',
|
||||
vifNoInterface: 'No interface',
|
||||
vifDeviceLabel: 'Device',
|
||||
vifMacLabel: 'MAC address',
|
||||
vifMtuLabel: 'MTU',
|
||||
@@ -1081,13 +1043,12 @@ const messages = {
|
||||
'Network locked and no IPs are allowed for this interface',
|
||||
vifUnLockedNetwork: 'Network not locked',
|
||||
vifUnknownNetwork: 'Unknown network',
|
||||
vifAction: 'Action',
|
||||
vifCreate: 'Create',
|
||||
|
||||
// ----- VM snapshot tab -----
|
||||
noSnapshots: 'No snapshots',
|
||||
newSnapshotWithMemory: 'New snapshot with memory',
|
||||
snapshotMemorySaved: 'memory saved',
|
||||
snapshotMemorySaved: 'Memory saved',
|
||||
snapshotCreateButton: 'New snapshot',
|
||||
tipCreateSnapshotLabel: 'Just click on the snapshot button to create one!',
|
||||
revertSnapshot: 'Revert VM to this snapshot',
|
||||
@@ -1098,7 +1059,6 @@ const messages = {
|
||||
snapshotDate: 'Creation date',
|
||||
snapshotName: 'Name',
|
||||
snapshotDescription: 'Description',
|
||||
snapshotAction: 'Action',
|
||||
snapshotQuiesce: 'Quiesced snapshot',
|
||||
vmRevertSuccessfulTitle: 'Revert successful',
|
||||
vmRevertSuccessfulMessage: 'VM successfully reverted',
|
||||
@@ -1159,10 +1119,8 @@ const messages = {
|
||||
vmCoresPerSocketIncorrectValue: 'Incorrect cores per socket value',
|
||||
vmCoresPerSocketIncorrectValueSolution:
|
||||
'Please change the selected value to fix it.',
|
||||
vmHaDisabled: 'disabled',
|
||||
vmHaDisabled: 'Disabled',
|
||||
vmMemoryLimitsLabel: 'Memory limits (min/max)',
|
||||
vmMaxVcpus: 'vCPUs max:',
|
||||
vmMaxRam: 'Memory max:',
|
||||
vmVgpu: 'vGPU',
|
||||
vmVgpus: 'GPUs',
|
||||
vmVgpuNone: 'None',
|
||||
@@ -1174,13 +1132,15 @@ const messages = {
|
||||
addAclsErrorMessage: 'User(s)/group(s) and role are required.',
|
||||
removeAcl: 'Delete',
|
||||
moreAcls: '{nAcls, number} more…',
|
||||
vmBootFirmware: 'Boot firmware',
|
||||
vmDefaultBootFirmwareLabel: 'default (bios)',
|
||||
vmBootFirmwareWarningMessage:
|
||||
"You're about to change your boot firmware. This is still experimental in CH/XCP-ng 8.0. Are you sure you want to continue?",
|
||||
|
||||
// ----- VM placeholders -----
|
||||
|
||||
vmHomeNamePlaceholder: 'Long click to add a name',
|
||||
vmHomeDescriptionPlaceholder: 'Long click to add a description',
|
||||
vmViewNamePlaceholder: 'Click to add a name',
|
||||
vmViewDescriptionPlaceholder: 'Click to add a description',
|
||||
|
||||
// ----- Templates -----
|
||||
|
||||
@@ -1258,9 +1218,6 @@ const messages = {
|
||||
statsDashboardSelectObjects: 'Select',
|
||||
metricsLoading: 'Loading…',
|
||||
|
||||
// ----- Visualizations -----
|
||||
comingSoon: 'Coming soon!',
|
||||
|
||||
// ----- Health -----
|
||||
orphanedVdis: 'Orphaned snapshot VDIs',
|
||||
orphanedVms: 'Orphaned VMs snapshot',
|
||||
@@ -1281,7 +1238,6 @@ const messages = {
|
||||
alarmContent: 'Content',
|
||||
alarmObject: 'Issue on',
|
||||
alarmPool: 'Pool',
|
||||
alarmRemoveAll: 'Remove all alarms',
|
||||
spaceLeftTooltip: '{used}% used ({free} left)',
|
||||
|
||||
// ----- New VM -----
|
||||
@@ -1339,7 +1295,6 @@ const messages = {
|
||||
newVmCreateVms: 'Create VMs',
|
||||
newVmCreateVmsConfirm: 'Are you sure you want to create {nbVms, number} VMs?',
|
||||
newVmMultipleVms: 'Multiple VMs:',
|
||||
newVmSelectResourceSet: 'Select a resource set:',
|
||||
newVmMultipleVmsPattern: 'Name pattern:',
|
||||
newVmMultipleVmsPatternPlaceholder: 'e.g.: \\{name\\}_%',
|
||||
newVmFirstIndex: 'First index:',
|
||||
@@ -1379,9 +1334,6 @@ const messages = {
|
||||
deleteResourceSetQuestion:
|
||||
'Are you sure you want to delete this resource set?',
|
||||
resourceSetMissingObjects: 'Missing objects:',
|
||||
resourceSetVcpus: 'vCPUs',
|
||||
resourceSetMemory: 'Memory',
|
||||
resourceSetStorage: 'Storage',
|
||||
unknownResourceSetValue: 'Unknown',
|
||||
availableHosts: 'Available hosts',
|
||||
excludedHosts: 'Excluded hosts',
|
||||
@@ -1393,8 +1345,6 @@ const messages = {
|
||||
maxDiskSpace: 'Maximum disk space',
|
||||
ipPool: 'IP pool',
|
||||
quantity: 'Quantity',
|
||||
noResourceSetLimits: 'No limits.',
|
||||
remainingResource: 'Remaining:',
|
||||
usedResourceLabel: 'Used',
|
||||
availableResourceLabel: 'Available',
|
||||
resourceSetQuota: 'Used: {usage} (Total: {total})',
|
||||
@@ -1457,7 +1407,6 @@ const messages = {
|
||||
listRemote: 'List Remote',
|
||||
simpleBackup: 'simple',
|
||||
delta: 'delta',
|
||||
restoreBackups: 'Restore Backups',
|
||||
noBackups: 'There are no backups!',
|
||||
restoreBackupsInfo: 'Click on a VM to display restore options',
|
||||
restoreDeltaBackupsInfo:
|
||||
@@ -1466,7 +1415,6 @@ const messages = {
|
||||
remoteDisabled: 'Disabled',
|
||||
enableRemote: 'Enable',
|
||||
disableRemote: 'Disable',
|
||||
remoteError: 'Error',
|
||||
remoteErrorMessage:
|
||||
'The URL ({url}) is invalid (colon in path). Click this button to change the URL to {newUrl}.',
|
||||
backupVmNameColumn: 'VM Name',
|
||||
@@ -1477,11 +1425,6 @@ const messages = {
|
||||
availableBackupsColumn: 'Available Backups',
|
||||
backupRestoreErrorTitle: 'Missing parameters',
|
||||
backupRestoreErrorMessage: 'Choose a SR and a backup',
|
||||
backupRestoreSelectDefaultSr: 'Select default SR…',
|
||||
backupRestoreChooseSrForEachVdis: 'Choose a SR for each VDI',
|
||||
backupRestoreVdiLabel: 'VDI',
|
||||
backupRestoreSrLabel: 'SR',
|
||||
displayBackup: 'Display backups',
|
||||
importBackupTitle: 'Import VM',
|
||||
importBackupMessage: 'Starting your backup import',
|
||||
vmsToBackup: 'VMs to backup',
|
||||
@@ -1515,7 +1458,6 @@ const messages = {
|
||||
'Are you sure you want to delete all the backups from {nVms, number} VM{nVms, plural, one {} other {s}}?',
|
||||
deleteVmBackupsBulkConfirmText:
|
||||
'delete {nBackups} backup{nBackups, plural, one {} other {s}}',
|
||||
unknownJob: 'Unknown job',
|
||||
bulkDeleteMetadataBackupsTitle: 'Delete metadata backups',
|
||||
bulkDeleteMetadataBackupsMessage:
|
||||
'Are you sure you want to delete all the backups from {nMetadataBackups, number} metadata backup{nMetadataBackups, plural, one {} other {s}}?',
|
||||
@@ -1530,9 +1472,7 @@ const messages = {
|
||||
restoreFilesSelectBackup: 'Select a backup…',
|
||||
restoreFilesSelectDisk: 'Select a disk…',
|
||||
restoreFilesSelectPartition: 'Select a partition…',
|
||||
restoreFilesSelectFolderPath: 'Folder path',
|
||||
restoreFilesSelectFiles: 'Select a file…',
|
||||
restoreFileContentNotFound: 'Content not found',
|
||||
restoreFilesNoFilesSelected: 'No files selected',
|
||||
restoreFilesSelectedFiles: 'Selected files ({files}):',
|
||||
restoreFilesSelectedFilesAndFolders: 'Selected files/folders ({files}):',
|
||||
@@ -1671,12 +1611,10 @@ const messages = {
|
||||
deleteOrphanedVdisModalTitle: 'Delete orphaned snapshot VDIs',
|
||||
deleteOrphanedVdisModalMessage:
|
||||
'Are you sure you want to delete {nVdis, number} orphaned snapshot VDI{nVdis, plural, one {} other {s}}?',
|
||||
removeAllLogsModalTitle: 'Remove all logs',
|
||||
removeAllLogsModalWarning: 'Are you sure you want to remove all logs?',
|
||||
definitiveMessageModal: 'This operation is definitive.',
|
||||
existingLunModalTitle: 'Previous LUN Usage',
|
||||
existingLunModalText:
|
||||
'This LUN has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation.',
|
||||
'This LUN has been previously used as storage by a XenServer host. All data will be lost if you choose to continue with the SR creation.',
|
||||
alreadyRegisteredModal: 'Replace current registration?',
|
||||
alreadyRegisteredModalText:
|
||||
'Your XO appliance is already registered to {email}, do you want to forget and replace this registration ?',
|
||||
@@ -1707,7 +1645,6 @@ const messages = {
|
||||
serverHost: 'Host',
|
||||
serverUsername: 'Username',
|
||||
serverPassword: 'Password',
|
||||
serverAction: 'Action',
|
||||
serverReadOnly: 'Read Only',
|
||||
serverUnauthorizedCertificates: 'Unauthorized Certificates',
|
||||
serverAllowUnauthorizedCertificates: 'Allow Unauthorized Certificates',
|
||||
@@ -1723,7 +1660,6 @@ const messages = {
|
||||
serverAddFailed: 'Adding server failed',
|
||||
serverStatus: 'Status',
|
||||
serverConnectionFailed: 'Connection failed. Click for more information.',
|
||||
serverConnecting: 'Connecting…',
|
||||
serverConnected: 'Connected',
|
||||
serverDisconnected: 'Disconnected',
|
||||
serverAuthFailed: 'Authentication error',
|
||||
@@ -1735,7 +1671,6 @@ const messages = {
|
||||
// ----- Copy VM -----
|
||||
copyVm: 'Copy VM',
|
||||
copyVmName: 'Name',
|
||||
copyVmNamePattern: 'Name pattern',
|
||||
copyVmNamePlaceholder: 'If empty: name of the copied VM',
|
||||
copyVmNamePatternPlaceholder: 'e.g.: "\\{name\\}_COPY"',
|
||||
copyVmSelectSr: 'Select SR',
|
||||
@@ -1786,12 +1721,11 @@ const messages = {
|
||||
addHostNoHostMessage: 'No host selected to be added',
|
||||
|
||||
// ----- About View -----
|
||||
xenOrchestra: 'Xen Orchestra',
|
||||
xenOrchestraServer: 'Xen Orchestra server',
|
||||
xenOrchestraWeb: 'Xen Orchestra web client',
|
||||
noProSupport: 'No pro support provided!',
|
||||
noProductionUse: 'Use in production at your own risks',
|
||||
downloadXoaFromWebsite: 'You can download our turnkey appliance at {website}',
|
||||
noProSupport: 'Professional support missing!',
|
||||
noProductionUse: 'Use in production at your own risk',
|
||||
downloadXoaFromWebsite: 'You can download the turnkey appliance at {website}',
|
||||
bugTracker: 'Bug Tracker',
|
||||
bugTrackerText: 'Issues? Report it!',
|
||||
community: 'Community',
|
||||
@@ -1817,7 +1751,6 @@ const messages = {
|
||||
'This feature is not available in your version, contact your administrator to know more.',
|
||||
|
||||
// ----- Updates View -----
|
||||
updateTitle: 'Updates',
|
||||
registration: 'Registration',
|
||||
settings: 'Settings',
|
||||
proxySettings: 'Proxy settings',
|
||||
@@ -1861,6 +1794,11 @@ const messages = {
|
||||
upgradeWarningTitle: 'Upgrade warning',
|
||||
upgradeWarningMessage:
|
||||
'You have some backup jobs in progress. If you upgrade now, these jobs will be interrupted! Are you sure you want to continue?',
|
||||
releaseChannels: 'Release channels',
|
||||
unlistedChannel: 'unlisted channel',
|
||||
unlistedChannelName: 'Unlisted channel name',
|
||||
selectChannel: 'Select channel',
|
||||
changeChannel: 'Change channel',
|
||||
|
||||
// ----- OS Disclaimer -----
|
||||
disclaimerTitle: 'Xen Orchestra from the sources',
|
||||
@@ -1907,11 +1845,15 @@ const messages = {
|
||||
pwdChangeErrorBody:
|
||||
'The old password provided is incorrect. Your password has not been changed.',
|
||||
changePasswordOk: 'OK',
|
||||
forgetTokens: 'Forget all connection tokens',
|
||||
forgetTokensExplained:
|
||||
'This will prevent other clients from authenticating with existing tokens but will not kill active sessions',
|
||||
forgetTokensSuccess: 'Successfully forgot connection tokens',
|
||||
forgetTokensError: 'Error while forgetting connection tokens',
|
||||
sshKeys: 'SSH keys',
|
||||
newSshKey: 'New SSH key',
|
||||
deleteSshKey: 'Delete',
|
||||
deleteSshKeys: 'Delete selected SSH keys',
|
||||
noSshKeys: 'No SSH keys',
|
||||
newSshKeyModalTitle: 'New SSH key',
|
||||
sshKeyErrorTitle: 'Invalid key',
|
||||
sshKeyErrorMessage: 'An SSH key requires both a title and a key.',
|
||||
@@ -1934,22 +1876,16 @@ const messages = {
|
||||
others: 'Others',
|
||||
|
||||
// ----- Logs -----
|
||||
loadingLogs: 'Loading logs…',
|
||||
logUser: 'User',
|
||||
logMethod: 'Method',
|
||||
logParams: 'Params',
|
||||
logMessage: 'Message',
|
||||
logSuggestXcpNg: 'Use XCP-ng to get rid of restrictions',
|
||||
logError: 'Error',
|
||||
logTitle: 'Logs',
|
||||
logDisplayDetails: 'Display details',
|
||||
logDownload: 'Download log',
|
||||
logTime: 'Date',
|
||||
logNoStackTrace: 'No stack trace',
|
||||
logNoParams: 'No params',
|
||||
logDelete: 'Delete log',
|
||||
logsDelete: 'Delete logs',
|
||||
logsThreePerPage: '3 / page',
|
||||
logsTenPerPage: '10 / page',
|
||||
logsJobId: 'Job ID',
|
||||
logsJobName: 'Job name',
|
||||
logsBackupTime: 'Backup time',
|
||||
@@ -1973,12 +1909,9 @@ const messages = {
|
||||
// ----- IPs ------
|
||||
ipPoolName: 'Name',
|
||||
ipPoolIps: 'IPs',
|
||||
ipPoolIpsPlaceholder: 'IPs (e.g.: 1.0.0.12-1.0.0.17;1.0.0.23)',
|
||||
ipPoolNetworks: 'Networks',
|
||||
ipsNoIpPool: 'No IP pools',
|
||||
ipsCreate: 'Create',
|
||||
ipsDeleteAllTitle: 'Delete all IP pools',
|
||||
ipsDeleteAllMessage: 'Are you sure you want to delete all the IP pools?',
|
||||
ipsVifs: 'VIFs',
|
||||
ipsNotUsed: 'Not used',
|
||||
ipPoolUnknownVif: 'unknown VIF',
|
||||
@@ -2027,7 +1960,7 @@ const messages = {
|
||||
// ----- Config -----
|
||||
noConfigFile: 'No config file selected',
|
||||
importTip:
|
||||
'Try dropping a config file here, or click to select a config file to upload.',
|
||||
'Try dropping a config file here or click to select a config file to upload.',
|
||||
config: 'Config',
|
||||
importConfig: 'Import',
|
||||
importConfigSuccess: 'Config file successfully imported',
|
||||
@@ -2039,8 +1972,6 @@ const messages = {
|
||||
// ----- SR -----
|
||||
srReconnectAllModalTitle: 'Reconnect all hosts',
|
||||
srReconnectAllModalMessage: 'This will reconnect this SR to all its hosts.',
|
||||
srsReconnectAllModalMessage:
|
||||
'This will reconnect each selected SR to its host (local SR) or to every hosts of its pool (shared SR).',
|
||||
srDisconnectAllModalTitle: 'Disconnect all hosts',
|
||||
srDisconnectAllModalMessage:
|
||||
'This will disconnect this SR from all its hosts.',
|
||||
@@ -2058,8 +1989,6 @@ const messages = {
|
||||
|
||||
// ----- XOSAN -----
|
||||
xosanTitle: 'XOSAN',
|
||||
xosanSrTitle: 'Xen Orchestra SAN SR',
|
||||
xosanAvailableSrsTitle: 'Select local SRs (lvm)',
|
||||
xosanSuggestions: 'Suggestions',
|
||||
xosanDisperseWarning:
|
||||
'Warning: using disperse layout is not recommended right now. Please read {link}.',
|
||||
@@ -2067,7 +1996,6 @@ const messages = {
|
||||
xosanHost: 'Host',
|
||||
xosanHosts: 'Connected Hosts',
|
||||
xosanPool: 'Pool',
|
||||
xosanVolumeId: 'Volume ID',
|
||||
xosanSize: 'Size',
|
||||
xosanUsedSpace: 'Used space',
|
||||
xosanLicense: 'License',
|
||||
@@ -2078,8 +2006,6 @@ const messages = {
|
||||
xosanNeedRestart:
|
||||
'Some hosts need their toolstack to be restarted before you can create an XOSAN',
|
||||
xosanRestartAgents: 'Restart toolstacks',
|
||||
xosanMasterOffline: 'Pool master is not running',
|
||||
xosanInstallPackTitle: 'Install XOSAN pack on {pool}',
|
||||
xosanSrOnSameHostMessage: 'Select no more than 1 SR per host',
|
||||
xosanLayout: 'Layout',
|
||||
xosanRedundancy: 'Redundancy',
|
||||
@@ -2087,8 +2013,6 @@ const messages = {
|
||||
xosanAvailableSpace: 'Available space',
|
||||
xosanDiskLossLegend: '* Can fail without data loss',
|
||||
xosanCreate: 'Create',
|
||||
xosanAdd: 'Add',
|
||||
xosanInstalling: 'Installing XOSAN. Please wait…',
|
||||
xosanCommunity: 'No XOSAN available for Community Edition',
|
||||
xosanNew: 'New',
|
||||
xosanAdvanced: 'Advanced',
|
||||
@@ -2104,7 +2028,7 @@ const messages = {
|
||||
xosanUpdatePacks: 'Update packs',
|
||||
xosanPackUpdateChecking: 'Checking for updates',
|
||||
xosanPackUpdateError:
|
||||
'Error while checking XOSAN packs. Please make sure that the Cloud plugin is installed and loaded and that the updater is reachable.',
|
||||
'Error while checking XOSAN packs. Please make sure that the Cloud plugin is installed and loaded, and that the updater is reachable.',
|
||||
xosanPackUpdateUnavailable: 'XOSAN resources are unavailable',
|
||||
xosanPackUpdateUnregistered: 'Not registered for XOSAN resources',
|
||||
xosanPackUpdateUpToDate: "✓ This pool's XOSAN packs are up to date!",
|
||||
@@ -2122,9 +2046,6 @@ const messages = {
|
||||
// Pack download modal
|
||||
xosanInstallCloudPlugin: 'Install cloud plugin first',
|
||||
xosanLoadCloudPlugin: 'Load cloud plugin first',
|
||||
xosanRegister: 'Register your appliance first',
|
||||
xosanLoading: 'Loading…',
|
||||
xosanNotAvailable: 'XOSAN is not available at the moment',
|
||||
xosanNoPackFound:
|
||||
'No compatible XOSAN pack found for your XenServer versions.',
|
||||
// SR tab XOSAN
|
||||
@@ -2169,7 +2090,6 @@ const messages = {
|
||||
'Will configure the host xosan network device with a static IP address and plug it in.',
|
||||
|
||||
// Licenses
|
||||
licensesTitle: 'Licenses',
|
||||
xosanUnregisteredDisclaimer:
|
||||
'You are not registered and therefore will not be able to create or manage your XOSAN SRs. {link}',
|
||||
xosanSourcesDisclaimer:
|
||||
|
||||
@@ -283,7 +283,7 @@ export const Vdi = decorate([
|
||||
sr: getSr(state, props),
|
||||
})
|
||||
}),
|
||||
({ id, sr, vdi }) => {
|
||||
({ id, showSize, showSr, sr, vdi }) => {
|
||||
if (vdi === undefined) {
|
||||
return unknowItem(id, 'VDI')
|
||||
}
|
||||
@@ -291,9 +291,12 @@ export const Vdi = decorate([
|
||||
return (
|
||||
<span>
|
||||
<Icon icon='disk' /> {vdi.name_label}
|
||||
{sr !== undefined && (
|
||||
{sr !== undefined && showSr && (
|
||||
<span className='text-muted'> - {sr.name_label}</span>
|
||||
)}
|
||||
{showSize && (
|
||||
<span className='text-muted'> ({formatSize(vdi.size)})</span>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
@@ -302,10 +305,13 @@ export const Vdi = decorate([
|
||||
Vdi.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
self: PropTypes.bool,
|
||||
showSize: PropTypes.bool,
|
||||
}
|
||||
|
||||
Vdi.defaultProps = {
|
||||
self: false,
|
||||
showSize: false,
|
||||
showSr: false,
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
@@ -432,8 +438,8 @@ const xoItemToRender = {
|
||||
// XO objects.
|
||||
pool: ({ id }) => <Pool id={id} />,
|
||||
|
||||
VDI: ({ id }) => <Vdi id={id} />,
|
||||
'VDI-resourceSet': ({ id }) => <Vdi id={id} self />,
|
||||
VDI: ({ id }) => <Vdi id={id} showSr />,
|
||||
'VDI-resourceSet': ({ id }) => <Vdi id={id} self showSr />,
|
||||
|
||||
// Pool objects.
|
||||
'VM-template': ({ id }) => <VmTemplate id={id} />,
|
||||
@@ -498,6 +504,9 @@ const xoItemToRender = {
|
||||
{backup.mode}
|
||||
</span>{' '}
|
||||
<span className='tag tag-warning'>{backup.remote.name}</span>{' '}
|
||||
{backup.size !== undefined && (
|
||||
<span className='tag tag-info'>{formatSize(backup.size)}</span>
|
||||
)}{' '}
|
||||
<FormattedDate
|
||||
value={new Date(backup.timestamp)}
|
||||
month='long'
|
||||
|
||||
53
packages/xo-web/src/common/select-boot-firmware.js
Normal file
53
packages/xo-web/src/common/select-boot-firmware.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import _ from 'intl'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import { confirm } from 'modal'
|
||||
import { injectState, provideState } from 'reaclette'
|
||||
import { noop } from 'utils'
|
||||
|
||||
// https://docs.citrix.com/en-us/citrix-hypervisor/whats-new/experimental.html
|
||||
// XAPI values should be lowercased
|
||||
const VM_BOOT_FIRMWARES = ['bios', 'uefi']
|
||||
|
||||
const withState = provideState({
|
||||
effects: {
|
||||
handleBootFirmwareChange(
|
||||
__,
|
||||
{
|
||||
target: { value },
|
||||
}
|
||||
) {
|
||||
if (value !== '') {
|
||||
// TODO: Confirm should be removed once the feature is stabilized
|
||||
confirm({
|
||||
title: _('vmBootFirmware'),
|
||||
body: _('vmBootFirmwareWarningMessage'),
|
||||
}).then(() => this.props.onChange(value), noop)
|
||||
} else {
|
||||
this.props.onChange(value)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const SelectBootFirmware = ({ effects, value }) => (
|
||||
<select
|
||||
className='form-control'
|
||||
onChange={effects.handleBootFirmwareChange}
|
||||
value={value}
|
||||
>
|
||||
<option value=''>{_('vmDefaultBootFirmwareLabel')}</option>
|
||||
{VM_BOOT_FIRMWARES.map(val => (
|
||||
<option key={val} value={val}>
|
||||
{val}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)
|
||||
|
||||
SelectBootFirmware.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export default withState(injectState(SelectBootFirmware))
|
||||
@@ -59,7 +59,7 @@ export const constructSmartPattern = (
|
||||
|
||||
const valueToComplexMatcher = pattern => {
|
||||
if (typeof pattern === 'string') {
|
||||
return new CM.RegExpNode(`^${escapeRegExp(pattern)}$`)
|
||||
return new CM.RegExpNode(`^${escapeRegExp(pattern)}$`, 'i')
|
||||
}
|
||||
|
||||
if (Array.isArray(pattern)) {
|
||||
|
||||
@@ -29,7 +29,7 @@ import _ from './intl'
|
||||
import * as actions from './store/actions'
|
||||
import invoke from './invoke'
|
||||
import store from './store'
|
||||
import { getObject } from './selectors'
|
||||
import { getObject, isAdmin } from './selectors'
|
||||
import { satisfies as versionSatisfies } from 'semver'
|
||||
|
||||
export const EMPTY_ARRAY = Object.freeze([])
|
||||
@@ -650,3 +650,12 @@ export const hasLicenseRestrictions = host =>
|
||||
host.productBrand !== 'XCP-ng' &&
|
||||
versionSatisfies(host.version, '>=7.3.0') &&
|
||||
host.license_params.sku_type === 'free'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export const adminOnly = Component =>
|
||||
connectStore({
|
||||
_isAdmin: isAdmin,
|
||||
})(({ _isAdmin, ...props }) =>
|
||||
_isAdmin ? <Component {...props} /> : <_NotFound />
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
max,
|
||||
round,
|
||||
size,
|
||||
sortBy,
|
||||
sum,
|
||||
values,
|
||||
} from 'lodash'
|
||||
@@ -107,7 +108,7 @@ const makeLabelInterpolationFnc = (intl, nValues, endTimestamp, interval) => {
|
||||
|
||||
// Supported series: xvds, vifs, pifs.
|
||||
const buildSeries = ({ stats, label, addSumSeries }) => {
|
||||
const series = []
|
||||
let series = []
|
||||
|
||||
for (const io in stats) {
|
||||
const ioData = stats[io]
|
||||
@@ -123,6 +124,8 @@ const buildSeries = ({ stats, label, addSumSeries }) => {
|
||||
}
|
||||
}
|
||||
|
||||
series = sortBy(series, 'name')
|
||||
|
||||
if (addSumSeries) {
|
||||
series.push({
|
||||
name: `All ${io}`,
|
||||
@@ -588,10 +591,13 @@ PoolLoadLineChart.propTypes = {
|
||||
}
|
||||
|
||||
const buildSrSeries = ({ stats, label, addSumSeries }) => {
|
||||
const series = map(stats, (data, key) => ({
|
||||
name: `${label} (${key})`,
|
||||
data,
|
||||
}))
|
||||
const series = sortBy(
|
||||
map(stats, (data, key) => ({
|
||||
name: `${label} (${key})`,
|
||||
data,
|
||||
})),
|
||||
'name'
|
||||
)
|
||||
|
||||
if (addSumSeries) {
|
||||
series.push({
|
||||
|
||||
@@ -3,6 +3,7 @@ import Component from 'base-component'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import { map } from 'lodash'
|
||||
import { Vdi } from 'render-xo-item'
|
||||
|
||||
import _ from '../../intl'
|
||||
import SingleLineRow from '../../single-line-row'
|
||||
@@ -85,7 +86,9 @@ export default class ChooseSrForEachVdisModal extends Component {
|
||||
</SingleLineRow>
|
||||
{map(props.vdis, vdi => (
|
||||
<SingleLineRow key={vdi.uuid}>
|
||||
<Col size={6}>{vdi.name_label || vdi.name}</Col>
|
||||
<Col size={6}>
|
||||
<Vdi id={vdi.id} showSize />
|
||||
</Col>
|
||||
<Col size={6}>
|
||||
<SelectSr
|
||||
onChange={sr =>
|
||||
|
||||
@@ -376,7 +376,7 @@ export const dismissNotification = id => {
|
||||
export const subscribeNotifications = createSubscription(async () => {
|
||||
const { user, xoaUpdaterState } = store.getState()
|
||||
if (
|
||||
process.env.XOA_PLAN === 5 ||
|
||||
+process.env.XOA_PLAN === 5 ||
|
||||
xoaUpdaterState === 'disconnected' ||
|
||||
xoaUpdaterState === 'error'
|
||||
) {
|
||||
@@ -2508,14 +2508,25 @@ export const editUser = (user, { email, password, permission }) =>
|
||||
subscribeUsers.forceRefresh
|
||||
)
|
||||
|
||||
const _signOutFromEverywhereElse = () =>
|
||||
_call('token.deleteAll', { except: cookies.get('token') })
|
||||
|
||||
export const signOutFromEverywhereElse = () =>
|
||||
_signOutFromEverywhereElse().then(
|
||||
() => success(_('forgetTokens'), _('forgetTokensSuccess')),
|
||||
() => error(_('forgetTokens'), _('forgetTokensError'))
|
||||
)
|
||||
|
||||
export const changePassword = (oldPassword, newPassword) =>
|
||||
_call('user.changePassword', {
|
||||
oldPassword,
|
||||
newPassword,
|
||||
}).then(
|
||||
() => success(_('pwdChangeSuccess'), _('pwdChangeSuccessBody')),
|
||||
() => error(_('pwdChangeError'), _('pwdChangeErrorBody'))
|
||||
)
|
||||
})
|
||||
.then(_signOutFromEverywhereElse)
|
||||
.then(
|
||||
() => success(_('pwdChangeSuccess'), _('pwdChangeSuccessBody')),
|
||||
() => error(_('pwdChangeError'), _('pwdChangeErrorBody'))
|
||||
)
|
||||
|
||||
const _setUserPreferences = preferences =>
|
||||
_call('user.set', {
|
||||
|
||||
@@ -381,6 +381,13 @@ class XoaUpdater extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
getReleaseChannels() {
|
||||
return this._call('getReleaseChannels').catch(error => {
|
||||
console.error('getReleaseChannels', error)
|
||||
return {}
|
||||
})
|
||||
}
|
||||
|
||||
async _call(...args) {
|
||||
const c = await this._open()
|
||||
try {
|
||||
|
||||
@@ -7,6 +7,7 @@ const DEFAULTS = {
|
||||
concurrency: 0,
|
||||
fullInterval: 0,
|
||||
offlineSnapshot: false,
|
||||
reportWhen: 'failure',
|
||||
timeout: 0,
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ import React from 'react'
|
||||
import SortedTable from 'sorted-table'
|
||||
import StateButton from 'state-button'
|
||||
import Tooltip from 'tooltip'
|
||||
import { adminOnly, connectStore, routes } from 'utils'
|
||||
import { Card, CardHeader, CardBlock } from 'card'
|
||||
import { confirm } from 'modal'
|
||||
import { connectStore, routes } from 'utils'
|
||||
import { constructQueryString } from 'smart-backup'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { createGetLoneSnapshots, createSelector } from 'selectors'
|
||||
@@ -430,8 +430,10 @@ export default routes('overview', {
|
||||
'restore/metadata': RestoreMetadata,
|
||||
'file-restore': FileRestore,
|
||||
health: Health,
|
||||
})(({ children }) => (
|
||||
<Page header={HEADER} title='backupPage' formatTitle>
|
||||
{children}
|
||||
</Page>
|
||||
))
|
||||
})(
|
||||
adminOnly(({ children }) => (
|
||||
<Page header={HEADER} title='backupPage' formatTitle>
|
||||
{children}
|
||||
</Page>
|
||||
))
|
||||
)
|
||||
|
||||
70
packages/xo-web/src/xo-app/backup-ng/new/_reportWhen.js
Normal file
70
packages/xo-web/src/xo-app/backup-ng/new/_reportWhen.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import _ from 'intl'
|
||||
import decorate from 'apply-decorators'
|
||||
import Icon from 'icon'
|
||||
import Link from 'link'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import Select from 'form/select'
|
||||
import Tooltip from 'tooltip'
|
||||
import { generateId } from 'reaclette-utils'
|
||||
import { injectState, provideState } from 'reaclette'
|
||||
|
||||
import { FormGroup } from './../utils'
|
||||
|
||||
const REPORT_WHEN_FILTER_OPTIONS = [
|
||||
{
|
||||
label: 'reportWhenAlways',
|
||||
value: 'always',
|
||||
},
|
||||
{
|
||||
label: 'reportWhenFailure',
|
||||
value: 'failure',
|
||||
},
|
||||
{
|
||||
label: 'reportWhenNever',
|
||||
value: 'never',
|
||||
},
|
||||
]
|
||||
|
||||
const getOptionRenderer = ({ label }) => <span>{_(label)}</span>
|
||||
|
||||
const ReportWhen = decorate([
|
||||
provideState({
|
||||
computed: {
|
||||
idInput: generateId,
|
||||
},
|
||||
}),
|
||||
injectState,
|
||||
({ state, onChange, value, ...props }) => (
|
||||
<FormGroup>
|
||||
<label htmlFor={state.idInput}>
|
||||
<strong>{_('reportWhen')}</strong>
|
||||
</label>{' '}
|
||||
<Tooltip content={_('pluginsWarning')}>
|
||||
<Link
|
||||
className='btn btn-primary btn-sm'
|
||||
target='_blank'
|
||||
to='/settings/plugins'
|
||||
>
|
||||
<Icon icon='menu-settings-plugins' />{' '}
|
||||
<strong>{_('pluginsSettings')}</strong>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<Select
|
||||
id={state.idInput}
|
||||
onChange={onChange}
|
||||
optionRenderer={getOptionRenderer}
|
||||
options={REPORT_WHEN_FILTER_OPTIONS}
|
||||
value={value}
|
||||
{...props}
|
||||
/>
|
||||
</FormGroup>
|
||||
),
|
||||
])
|
||||
|
||||
ReportWhen.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.string,
|
||||
}
|
||||
|
||||
export { ReportWhen as default }
|
||||
@@ -7,7 +7,6 @@ import Icon from 'icon'
|
||||
import Link from 'link'
|
||||
import moment from 'moment-timezone'
|
||||
import React from 'react'
|
||||
import Select from 'form/select'
|
||||
import Tooltip from 'tooltip'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import UserError from 'user-error'
|
||||
@@ -42,6 +41,7 @@ import {
|
||||
} from 'xo'
|
||||
|
||||
import NewSchedule from './new-schedule'
|
||||
import ReportWhen from './_reportWhen'
|
||||
import Schedules from './schedules'
|
||||
import SmartBackup from './smart-backup'
|
||||
import getSettingsWithNonDefaultValue from '../_getSettingsWithNonDefaultValue'
|
||||
@@ -115,23 +115,6 @@ const destructVmsPattern = pattern =>
|
||||
vms: destructPattern(pattern),
|
||||
}
|
||||
|
||||
const REPORT_WHEN_FILTER_OPTIONS = [
|
||||
{
|
||||
label: 'reportWhenAlways',
|
||||
value: 'always',
|
||||
},
|
||||
{
|
||||
label: 'reportWhenFailure',
|
||||
value: 'failure',
|
||||
},
|
||||
{
|
||||
label: 'reportWhenNever',
|
||||
value: 'never',
|
||||
},
|
||||
]
|
||||
|
||||
const getOptionRenderer = ({ label }) => <span>{_(label)}</span>
|
||||
|
||||
const createDoesRetentionExist = name => {
|
||||
const predicate = setting => setting[name] > 0
|
||||
return ({ propSettings, settings = propSettings }) => settings.some(predicate)
|
||||
@@ -541,7 +524,6 @@ export default decorate([
|
||||
formId: generateId,
|
||||
inputConcurrencyId: generateId,
|
||||
inputFullIntervalId: generateId,
|
||||
inputReportWhenId: generateId,
|
||||
inputTimeoutId: generateId,
|
||||
|
||||
vmsPattern: ({ _vmsPattern }, { job }) =>
|
||||
@@ -884,31 +866,13 @@ export default decorate([
|
||||
</ActionButton>
|
||||
</CardHeader>
|
||||
<CardBlock>
|
||||
<FormGroup>
|
||||
<label htmlFor={state.inputReportWhenId}>
|
||||
<strong>{_('reportWhen')}</strong>
|
||||
</label>{' '}
|
||||
<Tooltip content={_('pluginsWarning')}>
|
||||
<Link
|
||||
className='btn btn-primary btn-sm'
|
||||
target='_blank'
|
||||
to='/settings/plugins'
|
||||
>
|
||||
<Icon icon='menu-settings-plugins' />{' '}
|
||||
<strong>{_('pluginsSettings')}</strong>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<Select
|
||||
id={state.inputReportWhenId}
|
||||
labelKey='label'
|
||||
onChange={effects.setReportWhen}
|
||||
optionRenderer={getOptionRenderer}
|
||||
options={REPORT_WHEN_FILTER_OPTIONS}
|
||||
required
|
||||
value={reportWhen}
|
||||
valueKey='value'
|
||||
/>
|
||||
</FormGroup>
|
||||
<ReportWhen
|
||||
onChange={effects.setReportWhen}
|
||||
required
|
||||
// Handle improper value introduced by:
|
||||
// https://github.com/vatesfr/xen-orchestra/commit/753ee994f2948bbaca9d3161eaab82329a682773#diff-9c044ab8a42ed6576ea927a64c1ec3ebR105
|
||||
value={reportWhen === 'Never' ? 'never' : reportWhen}
|
||||
/>
|
||||
{state.displayAdvancedSettings && (
|
||||
<div>
|
||||
<FormGroup>
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
Ul,
|
||||
} from '../../utils'
|
||||
|
||||
import ReportWhen from '../_reportWhen'
|
||||
import Schedules from '../_schedules'
|
||||
|
||||
// A retention can be:
|
||||
@@ -51,6 +52,25 @@ const RETENTION_XO_METADATA = {
|
||||
valuePath: 'retentionXoMetadata',
|
||||
}
|
||||
|
||||
const GLOBAL_SETTING_KEY = ''
|
||||
|
||||
const setSettingsDefaultRetentions = (
|
||||
settings,
|
||||
{ modePoolMetadata, modeXoMetadata }
|
||||
) =>
|
||||
mapValues(settings, (setting, key) =>
|
||||
key !== GLOBAL_SETTING_KEY
|
||||
? {
|
||||
retentionPoolMetadata: modePoolMetadata
|
||||
? defined(setting.retentionPoolMetadata, DEFAULT_RETENTION)
|
||||
: undefined,
|
||||
retentionXoMetadata: modeXoMetadata
|
||||
? defined(setting.retentionXoMetadata, DEFAULT_RETENTION)
|
||||
: undefined,
|
||||
}
|
||||
: setting
|
||||
)
|
||||
|
||||
const getInitialState = () => ({
|
||||
_modePoolMetadata: undefined,
|
||||
_modeXoMetadata: undefined,
|
||||
@@ -79,28 +99,25 @@ export default decorate([
|
||||
return { showErrors: true }
|
||||
}
|
||||
|
||||
const {
|
||||
modePoolMetadata,
|
||||
modeXoMetadata,
|
||||
name,
|
||||
pools,
|
||||
remotes,
|
||||
schedules,
|
||||
settings,
|
||||
} = state
|
||||
await createMetadataBackupJob({
|
||||
name: state.name,
|
||||
pools: state.modePoolMetadata
|
||||
? constructPattern(state.pools)
|
||||
: undefined,
|
||||
remotes: constructPattern(state.remotes),
|
||||
xoMetadata: state.modeXoMetadata,
|
||||
schedules: mapValues(
|
||||
state.schedules,
|
||||
({ id, ...schedule }) => schedule
|
||||
),
|
||||
settings: mapValues(
|
||||
state.settings,
|
||||
({ retentionPoolMetadata, retentionXoMetadata }) => ({
|
||||
retentionPoolMetadata: state.modePoolMetadata
|
||||
? defined(retentionPoolMetadata, DEFAULT_RETENTION)
|
||||
: undefined,
|
||||
retentionXoMetadata: state.modeXoMetadata
|
||||
? defined(retentionXoMetadata, DEFAULT_RETENTION)
|
||||
: undefined,
|
||||
})
|
||||
),
|
||||
name,
|
||||
pools: modePoolMetadata ? constructPattern(pools) : undefined,
|
||||
remotes: constructPattern(remotes),
|
||||
schedules: mapValues(schedules, ({ id, ...schedule }) => schedule),
|
||||
settings: setSettingsDefaultRetentions(settings, {
|
||||
modePoolMetadata,
|
||||
modeXoMetadata,
|
||||
}),
|
||||
xoMetadata: modeXoMetadata,
|
||||
})
|
||||
},
|
||||
editJob: () => async (state, props) => {
|
||||
@@ -138,23 +155,17 @@ export default decorate([
|
||||
}),
|
||||
])
|
||||
|
||||
const { modePoolMetadata, modeXoMetadata, name, pools, remotes } = state
|
||||
await editMetadataBackupJob({
|
||||
id: props.job.id,
|
||||
name: state.name,
|
||||
pools: state.modePoolMetadata ? constructPattern(state.pools) : null,
|
||||
remotes: constructPattern(state.remotes),
|
||||
xoMetadata: state.modeXoMetadata,
|
||||
settings: mapValues(
|
||||
settings,
|
||||
({ retentionPoolMetadata, retentionXoMetadata }) => ({
|
||||
retentionPoolMetadata: state.modePoolMetadata
|
||||
? defined(retentionPoolMetadata, DEFAULT_RETENTION)
|
||||
: undefined,
|
||||
retentionXoMetadata: state.modeXoMetadata
|
||||
? defined(retentionXoMetadata, DEFAULT_RETENTION)
|
||||
: undefined,
|
||||
})
|
||||
),
|
||||
name,
|
||||
pools: modePoolMetadata ? constructPattern(pools) : null,
|
||||
remotes: constructPattern(remotes),
|
||||
settings: setSettingsDefaultRetentions(settings, {
|
||||
modePoolMetadata,
|
||||
modeXoMetadata,
|
||||
}),
|
||||
xoMetadata: modeXoMetadata,
|
||||
})
|
||||
},
|
||||
|
||||
@@ -169,6 +180,20 @@ export default decorate([
|
||||
setSettings: (_, _settings) => () => ({
|
||||
_settings,
|
||||
}),
|
||||
setGlobalSettings: ({ setSettings }, name, value) => ({
|
||||
settings = {},
|
||||
}) => {
|
||||
setSettings({
|
||||
...settings,
|
||||
[GLOBAL_SETTING_KEY]: {
|
||||
...settings[GLOBAL_SETTING_KEY],
|
||||
[name]: value,
|
||||
},
|
||||
})
|
||||
},
|
||||
setReportWhen({ setGlobalSettings }, { value }) {
|
||||
setGlobalSettings('reportWhen', value)
|
||||
},
|
||||
toggleMode: (_, { mode }) => state => ({
|
||||
[`_${mode}`]: !state[mode],
|
||||
}),
|
||||
@@ -265,6 +290,11 @@ export default decorate([
|
||||
missingSchedules,
|
||||
} = state.showErrors ? state : {}
|
||||
|
||||
const { reportWhen = 'failure' } = defined(
|
||||
() => state.settings[GLOBAL_SETTING_KEY],
|
||||
{}
|
||||
)
|
||||
|
||||
return (
|
||||
<form id={state.idForm}>
|
||||
<Container>
|
||||
@@ -357,6 +387,16 @@ export default decorate([
|
||||
)}
|
||||
</CardBlock>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>{_('newBackupSettings')}</CardHeader>
|
||||
<CardBlock>
|
||||
<ReportWhen
|
||||
onChange={effects.setReportWhen}
|
||||
required
|
||||
value={reportWhen}
|
||||
/>
|
||||
</CardBlock>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col mediumSize={6}>
|
||||
{state.modePoolMetadata && (
|
||||
|
||||
@@ -6,7 +6,7 @@ import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import SortedTable from 'sorted-table'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import { addSubscriptions, noop } from 'utils'
|
||||
import { addSubscriptions, formatSize, noop } from 'utils'
|
||||
import { confirm } from 'modal'
|
||||
import { error } from 'notification'
|
||||
import { FormattedDate } from 'react-intl'
|
||||
@@ -87,6 +87,12 @@ const BACKUPS_COLUMNS = [
|
||||
default: true,
|
||||
sortOrder: 'desc',
|
||||
},
|
||||
{
|
||||
name: _('labelSize'),
|
||||
itemRenderer: ({ size }) =>
|
||||
size !== undefined && size !== 0 && formatSize(size),
|
||||
sortCriteria: 'size',
|
||||
},
|
||||
{
|
||||
name: _('availableBackupsColumn'),
|
||||
itemRenderer: ({ count }) =>
|
||||
@@ -149,6 +155,7 @@ export default class Restore extends Component {
|
||||
})
|
||||
// TODO: perf
|
||||
let first, last
|
||||
let size = 0
|
||||
forEach(backupDataByVm, (data, vmId) => {
|
||||
first = { timestamp: Infinity }
|
||||
last = { timestamp: 0 }
|
||||
@@ -161,9 +168,13 @@ export default class Restore extends Component {
|
||||
first = backup
|
||||
}
|
||||
count[backup.mode] = (count[backup.mode] || 0) + 1
|
||||
|
||||
if (backup.size !== undefined) {
|
||||
size += backup.size
|
||||
}
|
||||
})
|
||||
|
||||
assign(data, { first, last, count, id: vmId })
|
||||
assign(data, { first, last, count, id: vmId, size })
|
||||
})
|
||||
|
||||
forEach(backupDataByVm, ({ backups }, vmId) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import Icon from 'icon'
|
||||
import Link from 'link'
|
||||
import Page from '../page'
|
||||
import React from 'react'
|
||||
import { routes } from 'utils'
|
||||
import { adminOnly, routes } from 'utils'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { NavLink, NavTabs } from 'nav'
|
||||
|
||||
@@ -68,10 +68,12 @@ const Backup = routes('overview', {
|
||||
overview: Overview,
|
||||
restore: MovingRestoreMessage,
|
||||
'file-restore': MovingFileRestoreMessage,
|
||||
})(({ children }) => (
|
||||
<Page header={HEADER} title='backupPage' formatTitle>
|
||||
{children}
|
||||
</Page>
|
||||
))
|
||||
})(
|
||||
adminOnly(({ children }) => (
|
||||
<Page header={HEADER} title='backupPage' formatTitle>
|
||||
{children}
|
||||
</Page>
|
||||
))
|
||||
)
|
||||
|
||||
export default Backup
|
||||
|
||||
@@ -750,7 +750,8 @@ export default class Home extends Component {
|
||||
new ComplexMatcher.Or(
|
||||
map(
|
||||
tags,
|
||||
tag => new ComplexMatcher.RegExp(`^${escapeRegExp(tag.id)}$`)
|
||||
tag =>
|
||||
new ComplexMatcher.RegExp(`^${escapeRegExp(tag.id)}$`, 'i')
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -112,6 +112,10 @@ export default class extends Component {
|
||||
return uniqPacks
|
||||
}
|
||||
)
|
||||
_isHtEnabled = createSelector(
|
||||
() => this.props.host.CPUs.flags,
|
||||
flags => /\bht\b/.test(flags)
|
||||
)
|
||||
_setRemoteSyslogHost = value => setRemoteSyslogHost(this.props.host, value)
|
||||
|
||||
render() {
|
||||
@@ -272,6 +276,14 @@ export default class extends Component {
|
||||
{host.cpus.cores} ({host.cpus.sockets})
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{_('hyperThreading')}</th>
|
||||
<td>
|
||||
{this._isHtEnabled()
|
||||
? _('stateEnabled')
|
||||
: _('stateDisabled')}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{_('hostManufacturerinfo')}</th>
|
||||
<Copiable tagName='td'>
|
||||
|
||||
@@ -118,7 +118,9 @@ export default ({ statsOverview, host, nVms, vmController, vms }) => {
|
||||
<Usage total={host.memory.size}>
|
||||
<UsageElement
|
||||
highlight
|
||||
tooltip={`XenServer (${formatSize(vmController.memory.size)})`}
|
||||
tooltip={`${host.productBrand} (${formatSize(
|
||||
vmController.memory.size
|
||||
)})`}
|
||||
value={vmController.memory.size}
|
||||
/>
|
||||
{map(vms, vm => (
|
||||
|
||||
@@ -4,7 +4,7 @@ import Page from '../page'
|
||||
import React from 'react'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { NavLink, NavTabs } from 'nav'
|
||||
import { routes } from 'utils'
|
||||
import { adminOnly, routes } from 'utils'
|
||||
|
||||
import Edit from './edit'
|
||||
import New from './new'
|
||||
@@ -43,10 +43,12 @@ const Jobs = routes('overview', {
|
||||
overview: Overview,
|
||||
schedules: Schedules,
|
||||
'schedules/:id/edit': EditSchedule,
|
||||
})(({ children }) => (
|
||||
<Page header={HEADER} title='jobsPage' formatTitle>
|
||||
{children}
|
||||
</Page>
|
||||
))
|
||||
})(
|
||||
adminOnly(({ children }) => (
|
||||
<Page header={HEADER} title='jobsPage' formatTitle>
|
||||
{children}
|
||||
</Page>
|
||||
))
|
||||
)
|
||||
|
||||
export default Jobs
|
||||
|
||||
@@ -10,6 +10,7 @@ import Link from 'link'
|
||||
import Page from '../page'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import SelectBootFirmware from 'select-boot-firmware'
|
||||
import store from 'store'
|
||||
import Tags from 'tags'
|
||||
import Tooltip from 'tooltip'
|
||||
@@ -334,6 +335,7 @@ export default class NewVm extends BaseComponent {
|
||||
cpuWeight: '',
|
||||
existingDisks: {},
|
||||
fastClone: true,
|
||||
hvmBootFirmware: '',
|
||||
installMethod: 'noConfigDrive',
|
||||
multipleVms: false,
|
||||
name_label: '',
|
||||
@@ -501,6 +503,8 @@ export default class NewVm extends BaseComponent {
|
||||
tags: state.tags,
|
||||
vgpuType: get(() => state.vgpuType.id),
|
||||
gpuGroup: get(() => state.vgpuType.gpuGroup),
|
||||
hvmBootFirmware:
|
||||
state.hvmBootFirmware === '' ? undefined : state.hvmBootFirmware,
|
||||
}
|
||||
|
||||
return state.multipleVms
|
||||
@@ -576,6 +580,7 @@ export default class NewVm extends BaseComponent {
|
||||
CPUs: template.CPUs.number,
|
||||
cpuCap: '',
|
||||
cpuWeight: '',
|
||||
hvmBootFirmware: defined(() => template.boot.firmware, ''),
|
||||
memoryDynamicMax: template.memory.dynamic[1],
|
||||
// installation
|
||||
installMethod:
|
||||
@@ -752,6 +757,11 @@ export default class NewVm extends BaseComponent {
|
||||
template => template && template.name_label === 'CoreOS'
|
||||
)
|
||||
|
||||
_isHvm = createSelector(
|
||||
() => this.state.template,
|
||||
template => template && template.virtualizationMode === 'hvm'
|
||||
)
|
||||
|
||||
// On change -------------------------------------------------------------------
|
||||
|
||||
_onChangeSshKeys = keys =>
|
||||
@@ -900,6 +910,8 @@ export default class NewVm extends BaseComponent {
|
||||
_getRedirectionUrl = id =>
|
||||
this.state.state.multipleVms ? '/home' : `/vms/${id}`
|
||||
|
||||
_handleBootFirmware = value => this._setState({ hvmBootFirmware: value })
|
||||
|
||||
// MAIN ------------------------------------------------------------------------
|
||||
|
||||
_renderHeader = () => {
|
||||
@@ -1630,6 +1642,7 @@ export default class NewVm extends BaseComponent {
|
||||
bootAfterCreate,
|
||||
cpuCap,
|
||||
cpuWeight,
|
||||
hvmBootFirmware,
|
||||
memoryDynamicMin,
|
||||
memoryDynamicMax,
|
||||
memoryStaticMax,
|
||||
@@ -1641,10 +1654,11 @@ export default class NewVm extends BaseComponent {
|
||||
share,
|
||||
showAdvanced,
|
||||
tags,
|
||||
template,
|
||||
} = this.state.state
|
||||
const { isAdmin } = this.props
|
||||
const { formatMessage } = this.props.intl
|
||||
const isHvm = this._isHvm()
|
||||
|
||||
return (
|
||||
<Section
|
||||
icon='new-vm-advanced'
|
||||
@@ -1827,7 +1841,7 @@ export default class NewVm extends BaseComponent {
|
||||
</Item>
|
||||
</SectionContent>
|
||||
),
|
||||
template && template.virtualizationMode === 'hvm' && (
|
||||
isHvm && (
|
||||
<SectionContent>
|
||||
<Item label={_('vmVgpu')}>
|
||||
<SelectVgpuType
|
||||
@@ -1837,6 +1851,16 @@ export default class NewVm extends BaseComponent {
|
||||
</Item>
|
||||
</SectionContent>
|
||||
),
|
||||
isHvm && (
|
||||
<SectionContent>
|
||||
<Item label={_('vmBootFirmware')}>
|
||||
<SelectBootFirmware
|
||||
onChange={this._handleBootFirmware}
|
||||
value={hvmBootFirmware}
|
||||
/>
|
||||
</Item>
|
||||
</SectionContent>
|
||||
),
|
||||
]}
|
||||
</Section>
|
||||
)
|
||||
|
||||
@@ -15,10 +15,10 @@ import store from 'store'
|
||||
import trim from 'lodash/trim'
|
||||
import Wizard, { Section } from 'wizard'
|
||||
import { confirm } from 'modal'
|
||||
import { connectStore, formatSize } from 'utils'
|
||||
import { adminOnly, connectStore, formatSize } from 'utils'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import { Password, Select, Toggle } from 'form'
|
||||
import { Password, Select } from 'form'
|
||||
import { SelectHost } from 'select-objects'
|
||||
import {
|
||||
createFilter,
|
||||
@@ -46,6 +46,10 @@ import {
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const NFS_VERSIONS = ['4', '4.1']
|
||||
|
||||
// ===================================================================
|
||||
|
||||
class SelectScsiId extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
@@ -216,6 +220,7 @@ const getSrPath = id => (id !== undefined ? `/srs/${id}` : undefined)
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@adminOnly
|
||||
@injectIntl
|
||||
@connectStore(() => ({
|
||||
hosts: createGetObjectsOfType('host'),
|
||||
@@ -236,6 +241,7 @@ export default class New extends Component {
|
||||
lockCreation: undefined,
|
||||
lun: undefined,
|
||||
luns: undefined,
|
||||
nfsVersion: '',
|
||||
hbaDevices: undefined,
|
||||
name: undefined,
|
||||
path: undefined,
|
||||
@@ -266,7 +272,16 @@ export default class New extends Component {
|
||||
server,
|
||||
username,
|
||||
} = this.refs
|
||||
const { host, iqn, lun, path, type, scsiId, nfs4, nfsOptions } = this.state
|
||||
const {
|
||||
host,
|
||||
iqn,
|
||||
lun,
|
||||
nfsOptions,
|
||||
nfsVersion,
|
||||
path,
|
||||
scsiId,
|
||||
type,
|
||||
} = this.state
|
||||
|
||||
const createMethodFactories = {
|
||||
nfs: () =>
|
||||
@@ -276,7 +291,7 @@ export default class New extends Component {
|
||||
description.value,
|
||||
server.value,
|
||||
path,
|
||||
nfs4 ? '4' : undefined,
|
||||
nfsVersion !== '' ? nfsVersion : undefined,
|
||||
nfsOptions
|
||||
),
|
||||
hba: async () => {
|
||||
@@ -521,6 +536,12 @@ export default class New extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
_handleNfsVersion = ({ target: { value } }) => {
|
||||
this.setState({
|
||||
nfsVersion: value,
|
||||
})
|
||||
}
|
||||
|
||||
_reattach = async uuid => {
|
||||
const { host, type } = this.state
|
||||
|
||||
@@ -565,6 +586,7 @@ export default class New extends Component {
|
||||
lockCreation,
|
||||
lun,
|
||||
luns,
|
||||
nfsVersion,
|
||||
path,
|
||||
paths,
|
||||
summary,
|
||||
@@ -656,10 +678,22 @@ export default class New extends Component {
|
||||
</div>
|
||||
</fieldset>,
|
||||
<fieldset>
|
||||
<label>{_('newSrUseNfs4')}</label>
|
||||
<div>
|
||||
<Toggle onChange={this.toggleState('nfs4')} />
|
||||
</div>
|
||||
<label htmlFor='selectNfsVersion'>{_('newSrNfs')}</label>
|
||||
<select
|
||||
className='form-control'
|
||||
id='selectNfsVersion'
|
||||
onChange={this._handleNfsVersion}
|
||||
value={nfsVersion}
|
||||
>
|
||||
<option value=''>
|
||||
{formatMessage(messages.newSrNfsDefaultVersion)}
|
||||
</option>
|
||||
{map(NFS_VERSIONS, option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</fieldset>,
|
||||
<fieldset>
|
||||
<label>{_('newSrNfsOptions')}</label>
|
||||
|
||||
@@ -25,7 +25,12 @@ import Upgrade from 'xoa-upgrade'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import { SizeInput } from 'form'
|
||||
import { addSubscriptions, connectStore, resolveIds } from 'utils'
|
||||
import {
|
||||
addSubscriptions,
|
||||
adminOnly,
|
||||
connectStore,
|
||||
resolveIds
|
||||
} from 'utils'
|
||||
import {
|
||||
createGetObjectsOfType,
|
||||
createSelector,
|
||||
@@ -700,6 +705,7 @@ class ResourceSet extends Component {
|
||||
|
||||
const compareName = (a, b) => (a.name < b.name ? -1 : 1)
|
||||
|
||||
@adminOnly
|
||||
@addSubscriptions({ resourceSets: subscribeResourceSets })
|
||||
@connectStore({ resolvedResourceSets: getResolvedResourceSets })
|
||||
export default class Self extends Component {
|
||||
|
||||
@@ -2,7 +2,7 @@ import _ from 'intl'
|
||||
import Icon from 'icon'
|
||||
import Page from '../page'
|
||||
import React from 'react'
|
||||
import { routes } from 'utils'
|
||||
import { adminOnly, routes } from 'utils'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { NavLink, NavTabs } from 'nav'
|
||||
|
||||
@@ -74,10 +74,12 @@ const Settings = routes('servers', {
|
||||
remotes: Remotes,
|
||||
servers: Servers,
|
||||
users: Users,
|
||||
})(({ children }) => (
|
||||
<Page header={HEADER} title='settingsPage' formatTitle>
|
||||
{children}
|
||||
</Page>
|
||||
))
|
||||
})(
|
||||
adminOnly(({ children }) => (
|
||||
<Page header={HEADER} title='settingsPage' formatTitle>
|
||||
{children}
|
||||
</Page>
|
||||
))
|
||||
)
|
||||
|
||||
export default Settings
|
||||
|
||||
@@ -51,9 +51,20 @@ const COLUMNS = [
|
||||
{
|
||||
name: _('logMessage'),
|
||||
itemRenderer: log => (
|
||||
<pre className={styles.widthLimit}>
|
||||
{log.data.error && log.data.error.message}
|
||||
</pre>
|
||||
<span>
|
||||
<pre className={styles.widthLimit}>
|
||||
{log.data.error && log.data.error.message}
|
||||
</pre>
|
||||
{log.data.error && log.data.error.code === 'LICENCE_RESTRICTION' && (
|
||||
<a
|
||||
href='https://xcp-ng.org/'
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{_('logSuggestXcpNg')}
|
||||
</a>
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
sortCriteria: log => log.data.error && log.data.error.message,
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ import Icon from 'icon'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import SortedTable from 'sorted-table'
|
||||
import Tooltip from 'tooltip'
|
||||
import { Text } from 'editable'
|
||||
import { alert } from 'modal'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
editCustomFilter,
|
||||
removeCustomFilter,
|
||||
setDefaultHomeFilter,
|
||||
signOutFromEverywhereElse,
|
||||
subscribeCurrentUser,
|
||||
} from 'xo'
|
||||
|
||||
@@ -400,6 +402,20 @@ export default class User extends Component {
|
||||
</Col>
|
||||
</Row>
|
||||
<br />
|
||||
<Row>
|
||||
<Col smallSize={10} offset={2}>
|
||||
<Tooltip content={_('forgetTokensExplained')}>
|
||||
<ActionButton
|
||||
btnStyle='danger'
|
||||
handler={signOutFromEverywhereElse}
|
||||
icon='disconnect'
|
||||
>
|
||||
{_('forgetTokens')}
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
</Row>
|
||||
<br />
|
||||
<Row>
|
||||
<Col smallSize={2}>
|
||||
<strong>{_('language')}</strong>
|
||||
|
||||
@@ -8,6 +8,7 @@ import Icon from 'icon'
|
||||
import Link from 'link'
|
||||
import React from 'react'
|
||||
import renderXoItem from 'render-xo-item'
|
||||
import SelectBootFirmware from 'select-boot-firmware'
|
||||
import TabButton from 'tab-button'
|
||||
import Tooltip from 'tooltip'
|
||||
import { error } from 'notification'
|
||||
@@ -510,6 +511,11 @@ export default class TabAdvanced extends Component {
|
||||
_onChangeCpuMask = cpuMask =>
|
||||
editVm(this.props.vm, { cpuMask: map(cpuMask, 'value') })
|
||||
|
||||
_handleBootFirmware = value =>
|
||||
editVm(this.props.vm, {
|
||||
hvmBootFirmware: value !== '' ? value : null,
|
||||
})
|
||||
|
||||
_onNicTypeChange = value =>
|
||||
editVm(this.props.vm, { nicType: value === '' ? null : value })
|
||||
|
||||
@@ -833,6 +839,17 @@ export default class TabAdvanced extends Component {
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{vm.virtualizationMode === 'hvm' && (
|
||||
<tr>
|
||||
<th>{_('vmBootFirmware')}</th>
|
||||
<td>
|
||||
<SelectBootFirmware
|
||||
onChange={this._handleBootFirmware}
|
||||
value={defined(() => vm.boot.firmware, '')}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
|
||||
@@ -59,160 +59,178 @@ export default connectStore(() => {
|
||||
vgpuTypes,
|
||||
vm,
|
||||
vmTotalDiskSpace,
|
||||
}) => (
|
||||
<Container>
|
||||
{/* TODO: use CSS style */}
|
||||
<br />
|
||||
<Row className='text-xs-center'>
|
||||
<Col mediumSize={3}>
|
||||
<h2>
|
||||
<Number
|
||||
value={vm.CPUs.number}
|
||||
onChange={vcpus => editVm(vm, { CPUs: vcpus })}
|
||||
/>
|
||||
x <Icon icon='cpu' size='lg' />
|
||||
</h2>
|
||||
<BlockLink to={`/vms/${vm.id}/stats`}>
|
||||
{statsOverview && <CpuSparkLines data={statsOverview} />}
|
||||
</BlockLink>
|
||||
</Col>
|
||||
<Col mediumSize={3}>
|
||||
<h2 className='form-inline'>
|
||||
<Size
|
||||
value={defined(vm.memory.dynamic[1], null)}
|
||||
onChange={memory => editVm(vm, { memory })}
|
||||
/>
|
||||
|
||||
<span>
|
||||
<Icon icon='memory' size='lg' />
|
||||
</span>
|
||||
</h2>
|
||||
<BlockLink to={`/vms/${vm.id}/stats`}>
|
||||
{statsOverview && <MemorySparkLines data={statsOverview} />}
|
||||
</BlockLink>
|
||||
</Col>
|
||||
<Col mediumSize={3}>
|
||||
<BlockLink to={`/vms/${vm.id}/network`}>
|
||||
}) => {
|
||||
const {
|
||||
addresses,
|
||||
CPUs: cpus,
|
||||
current_operations: currentOperations,
|
||||
id,
|
||||
installTime,
|
||||
memory,
|
||||
os_version: osVersion,
|
||||
power_state: powerState,
|
||||
startTime,
|
||||
tags,
|
||||
VIFs: vifs,
|
||||
xenTools,
|
||||
} = vm
|
||||
return (
|
||||
<Container>
|
||||
{/* TODO: use CSS style */}
|
||||
<br />
|
||||
<Row className='text-xs-center'>
|
||||
<Col mediumSize={3}>
|
||||
<h2>
|
||||
{vm.VIFs.length}x <Icon icon='network' size='lg' />
|
||||
<Number
|
||||
value={cpus.number}
|
||||
onChange={vcpus => editVm(vm, { CPUs: vcpus })}
|
||||
/>
|
||||
x <Icon icon='cpu' size='lg' />
|
||||
</h2>
|
||||
</BlockLink>
|
||||
<BlockLink to={`/vms/${vm.id}/stats`}>
|
||||
{statsOverview && <NetworkSparkLines data={statsOverview} />}
|
||||
</BlockLink>
|
||||
</Col>
|
||||
<Col mediumSize={3}>
|
||||
<BlockLink to={`/vms/${vm.id}/disks`}>
|
||||
<h2>
|
||||
{formatSize(vmTotalDiskSpace)} <Icon icon='disk' size='lg' />
|
||||
<BlockLink to={`/vms/${id}/stats`}>
|
||||
{statsOverview && <CpuSparkLines data={statsOverview} />}
|
||||
</BlockLink>
|
||||
</Col>
|
||||
<Col mediumSize={3}>
|
||||
<h2 className='form-inline'>
|
||||
<Size
|
||||
value={defined(memory.dynamic[1], null)}
|
||||
onChange={memory => editVm(vm, { memory })}
|
||||
/>
|
||||
|
||||
<span>
|
||||
<Icon icon='memory' size='lg' />
|
||||
</span>
|
||||
</h2>
|
||||
</BlockLink>
|
||||
<BlockLink to={`/vms/${vm.id}/stats`}>
|
||||
{statsOverview && <XvdSparkLines data={statsOverview} />}
|
||||
</BlockLink>
|
||||
</Col>
|
||||
</Row>
|
||||
{/* TODO: use CSS style */}
|
||||
<br />
|
||||
<Row className='text-xs-center'>
|
||||
<Col mediumSize={3}>
|
||||
{vm.installTime !== null && (
|
||||
<div className='text-xs-center'>
|
||||
{_('created', {
|
||||
date: (
|
||||
<FormattedDate
|
||||
day='2-digit'
|
||||
month='long'
|
||||
value={vm.installTime * 1000}
|
||||
year='numeric'
|
||||
/>
|
||||
),
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{vm.power_state === 'Running' ? (
|
||||
<div>
|
||||
<p className='text-xs-center'>
|
||||
{_('started', {
|
||||
ago: <FormattedRelative value={vm.startTime * 1000} />,
|
||||
<BlockLink to={`/vms/${id}/stats`}>
|
||||
{statsOverview && <MemorySparkLines data={statsOverview} />}
|
||||
</BlockLink>
|
||||
</Col>
|
||||
<Col mediumSize={3}>
|
||||
<BlockLink to={`/vms/${id}/network`}>
|
||||
<h2>
|
||||
{vifs.length}x <Icon icon='network' size='lg' />
|
||||
</h2>
|
||||
</BlockLink>
|
||||
<BlockLink to={`/vms/${id}/stats`}>
|
||||
{statsOverview && <NetworkSparkLines data={statsOverview} />}
|
||||
</BlockLink>
|
||||
</Col>
|
||||
<Col mediumSize={3}>
|
||||
<BlockLink to={`/vms/${id}/disks`}>
|
||||
<h2>
|
||||
{formatSize(vmTotalDiskSpace)} <Icon icon='disk' size='lg' />
|
||||
</h2>
|
||||
</BlockLink>
|
||||
<BlockLink to={`/vms/${id}/stats`}>
|
||||
{statsOverview && <XvdSparkLines data={statsOverview} />}
|
||||
</BlockLink>
|
||||
</Col>
|
||||
</Row>
|
||||
{/* TODO: use CSS style */}
|
||||
<br />
|
||||
<Row className='text-xs-center'>
|
||||
<Col mediumSize={3}>
|
||||
{installTime !== null && (
|
||||
<div className='text-xs-center'>
|
||||
{_('created', {
|
||||
date: (
|
||||
<FormattedDate
|
||||
day='2-digit'
|
||||
month='long'
|
||||
value={installTime * 1000}
|
||||
year='numeric'
|
||||
/>
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className='text-xs-center'>
|
||||
{lastShutdownTime
|
||||
? _('vmHaltedSince', {
|
||||
ago: <FormattedRelative value={lastShutdownTime * 1000} />,
|
||||
})
|
||||
: _('vmNotRunning')}
|
||||
</p>
|
||||
)}
|
||||
</Col>
|
||||
<Col mediumSize={3}>
|
||||
<p>{_(getVirtualizationModeLabel(vm))}</p>
|
||||
{vgpu !== undefined && (
|
||||
<p>{renderXoItem(vgpuTypes[vgpu.vgpuType])}</p>
|
||||
)}
|
||||
</Col>
|
||||
<Col mediumSize={3}>
|
||||
<BlockLink to={`/vms/${vm.id}/network`}>
|
||||
{vm.addresses && vm.addresses['0/ip'] ? (
|
||||
<Copiable tagName='p'>{vm.addresses['0/ip']}</Copiable>
|
||||
) : (
|
||||
<p>{_('noIpv4Record')}</p>
|
||||
</div>
|
||||
)}
|
||||
</BlockLink>
|
||||
</Col>
|
||||
<Col mediumSize={3}>
|
||||
<BlockLink to={`/vms/${vm.id}/advanced`}>
|
||||
<Tooltip
|
||||
content={vm.os_version ? vm.os_version.name : _('unknownOsName')}
|
||||
>
|
||||
<h1>
|
||||
<Icon
|
||||
className='text-info'
|
||||
icon={
|
||||
vm.os_version &&
|
||||
vm.os_version.distro &&
|
||||
osFamily(vm.os_version.distro)
|
||||
}
|
||||
/>
|
||||
</h1>
|
||||
</Tooltip>
|
||||
</BlockLink>
|
||||
</Col>
|
||||
</Row>
|
||||
{!vm.xenTools && vm.power_state === 'Running' && (
|
||||
<Row className='text-xs-center'>
|
||||
<Col>
|
||||
<Icon icon='error' />
|
||||
<em> {_('noToolsDetected')}.</em>
|
||||
{powerState === 'Running' || powerState === 'Paused' ? (
|
||||
<div>
|
||||
<p className='text-xs-center'>
|
||||
{_('started', {
|
||||
ago: <FormattedRelative value={startTime * 1000} />,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className='text-xs-center'>
|
||||
{lastShutdownTime
|
||||
? _('vmHaltedSince', {
|
||||
ago: (
|
||||
<FormattedRelative value={lastShutdownTime * 1000} />
|
||||
),
|
||||
})
|
||||
: _('vmNotRunning')}
|
||||
</p>
|
||||
)}
|
||||
</Col>
|
||||
<Col mediumSize={3}>
|
||||
<p>{_(getVirtualizationModeLabel(vm))}</p>
|
||||
{vgpu !== undefined && (
|
||||
<p>{renderXoItem(vgpuTypes[vgpu.vgpuType])}</p>
|
||||
)}
|
||||
</Col>
|
||||
<Col mediumSize={3}>
|
||||
<BlockLink to={`/vms/${id}/network`}>
|
||||
{addresses && addresses['0/ip'] ? (
|
||||
<Copiable tagName='p'>{addresses['0/ip']}</Copiable>
|
||||
) : (
|
||||
<p>{_('noIpv4Record')}</p>
|
||||
)}
|
||||
</BlockLink>
|
||||
</Col>
|
||||
<Col mediumSize={3}>
|
||||
<BlockLink to={`/vms/${id}/advanced`}>
|
||||
<Tooltip
|
||||
content={osVersion ? osVersion.name : _('unknownOsName')}
|
||||
>
|
||||
<h1>
|
||||
<Icon
|
||||
className='text-info'
|
||||
icon={
|
||||
osVersion &&
|
||||
osVersion.distro &&
|
||||
osFamily(osVersion.distro)
|
||||
}
|
||||
/>
|
||||
</h1>
|
||||
</Tooltip>
|
||||
</BlockLink>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{/* TODO: use CSS style */}
|
||||
<br />
|
||||
<Row>
|
||||
<Col>
|
||||
<h2 className='text-xs-center'>
|
||||
<HomeTags
|
||||
type='VM'
|
||||
labels={vm.tags}
|
||||
onDelete={tag => removeTag(vm.id, tag)}
|
||||
onAdd={tag => addTag(vm.id, tag)}
|
||||
/>
|
||||
</h2>
|
||||
</Col>
|
||||
</Row>
|
||||
{isEmpty(vm.current_operations) ? null : (
|
||||
<Row className='text-xs-center'>
|
||||
{!xenTools && powerState === 'Running' && (
|
||||
<Row className='text-xs-center'>
|
||||
<Col>
|
||||
<Icon icon='error' />
|
||||
<em> {_('noToolsDetected')}.</em>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{/* TODO: use CSS style */}
|
||||
<br />
|
||||
<Row>
|
||||
<Col>
|
||||
<h4>
|
||||
{_('vmCurrentStatus')} {map(vm.current_operations)[0]}
|
||||
</h4>
|
||||
<h2 className='text-xs-center'>
|
||||
<HomeTags
|
||||
type='VM'
|
||||
labels={tags}
|
||||
onDelete={tag => removeTag(id, tag)}
|
||||
onAdd={tag => addTag(id, tag)}
|
||||
/>
|
||||
</h2>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
{isEmpty(currentOperations) ? null : (
|
||||
<Row className='text-xs-center'>
|
||||
<Col>
|
||||
<h4>
|
||||
{_('vmCurrentStatus')} {map(currentOperations)[0]}
|
||||
</h4>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -37,6 +37,8 @@ import {
|
||||
map,
|
||||
remove,
|
||||
some,
|
||||
uniq,
|
||||
values,
|
||||
} from 'lodash'
|
||||
|
||||
import {
|
||||
@@ -515,6 +517,14 @@ export default class TabNetwork extends BaseComponent {
|
||||
newVif: !this.state.newVif,
|
||||
})
|
||||
|
||||
_getIpAddresses = createSelector(
|
||||
() => this.props.vm.addresses,
|
||||
// VM_guest_metrics.networks seems to always have 3 fields (ip, ipv4 and ipv6) for each interface
|
||||
// http://xenbits.xenproject.org/docs/4.12-testing/misc/xenstore-paths.html#attrvifdevidipv4index-ipv4_address-w
|
||||
// https://github.com/xapi-project/xen-api/blob/d650621ba7b64a82aeb77deca787acb059636eaf/ocaml/xapi/xapi_guest_agent.ml#L76-L79
|
||||
addresses => uniq(values(addresses))
|
||||
)
|
||||
|
||||
render() {
|
||||
const { newVif } = this.state
|
||||
const { pool, vm, vifs, networks } = this.props
|
||||
@@ -551,8 +561,8 @@ export default class TabNetwork extends BaseComponent {
|
||||
{!isEmpty(vm.addresses) ? (
|
||||
<span>
|
||||
<h4>{_('vifIpAddresses')}</h4>
|
||||
{map(vm.addresses, (address, key) => (
|
||||
<span key={key} className='tag tag-info tag-ip'>
|
||||
{map(this._getIpAddresses(), address => (
|
||||
<span key={address} className='tag tag-info tag-ip'>
|
||||
{address}
|
||||
</span>
|
||||
))}
|
||||
|
||||
@@ -6,7 +6,13 @@ import Link from 'link'
|
||||
import React from 'react'
|
||||
import renderXoItem from 'render-xo-item'
|
||||
import SortedTable from 'sorted-table'
|
||||
import { addSubscriptions, connectStore, getXoaPlan, ShortDate } from 'utils'
|
||||
import {
|
||||
addSubscriptions,
|
||||
adminOnly,
|
||||
connectStore,
|
||||
getXoaPlan,
|
||||
ShortDate,
|
||||
} from 'utils'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { createSelector, createGetObjectsOfType } from 'selectors'
|
||||
import { find, forEach } from 'lodash'
|
||||
@@ -77,6 +83,7 @@ const getBoundXosanRenderer = (boundObjectId, xosanSrs) => {
|
||||
return () => <Link to={`srs/${sr.id}`}>{renderXoItem(sr)}</Link>
|
||||
}
|
||||
|
||||
@adminOnly
|
||||
@connectStore({
|
||||
xosanSrs: createGetObjectsOfType('SR').filter([
|
||||
({ SR_type }) => SR_type === 'xosan', // eslint-disable-line camelcase
|
||||
|
||||
@@ -9,16 +9,17 @@ import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import Tooltip from 'tooltip'
|
||||
import xoaUpdater, { exposeTrial, isTrialRunning } from 'xoa-updater'
|
||||
import { addSubscriptions, connectStore } from 'utils'
|
||||
import { addSubscriptions, adminOnly, connectStore } from 'utils'
|
||||
import { Card, CardBlock, CardHeader } from 'card'
|
||||
import { confirm } from 'modal'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { error } from 'notification'
|
||||
import { generateId, linkState, toggleState } from 'reaclette-utils'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import { injectState, provideState } from 'reaclette'
|
||||
import { Input as DebounceInput } from 'debounce-input-decorator'
|
||||
import { isEmpty, map, pick, some, zipObject } from 'lodash'
|
||||
import { linkState, toggleState } from 'reaclette-utils'
|
||||
import { Password } from 'form'
|
||||
import { Password, Select } from 'form'
|
||||
import { subscribeBackupNgJobs, subscribeJobs } from 'xo'
|
||||
|
||||
const ansiUp = new AnsiUp()
|
||||
@@ -59,16 +60,22 @@ const LEVELS_TO_CLASSES = {
|
||||
error: 'text-danger',
|
||||
}
|
||||
|
||||
const UNLISTED_CHANNEL_VALUE = ''
|
||||
|
||||
const PROXY_ENTRIES = ['proxyHost', 'proxyPassword', 'proxyPort', 'proxyUser']
|
||||
const initialProxyState = () => zipObject(PROXY_ENTRIES)
|
||||
|
||||
const REGISTRATION_ENTRIES = ['email', 'password']
|
||||
const initialRegistrationState = () => zipObject(REGISTRATION_ENTRIES)
|
||||
|
||||
const CHANNEL_ENTRIES = ['channel']
|
||||
const initialChannelState = () => zipObject(CHANNEL_ENTRIES)
|
||||
|
||||
const helper = (obj1, obj2, prop) =>
|
||||
defined(() => obj1[prop], () => obj2[prop], '')
|
||||
|
||||
const Updates = decorate([
|
||||
adminOnly,
|
||||
addSubscriptions({
|
||||
backupNgJobs: subscribeBackupNgJobs,
|
||||
jobs: subscribeJobs,
|
||||
@@ -82,6 +89,7 @@ const Updates = decorate([
|
||||
]),
|
||||
provideState({
|
||||
initialState: () => ({
|
||||
...initialChannelState(),
|
||||
...initialProxyState(),
|
||||
...initialRegistrationState(),
|
||||
askRegisterAgain: false,
|
||||
@@ -89,22 +97,33 @@ const Updates = decorate([
|
||||
}),
|
||||
effects: {
|
||||
async configure() {
|
||||
await xoaUpdater.configure(
|
||||
pick(this.state, [
|
||||
await xoaUpdater.configure({
|
||||
...pick(this.state, [
|
||||
'channel',
|
||||
'proxyHost',
|
||||
'proxyPassword',
|
||||
'proxyPort',
|
||||
'proxyUser',
|
||||
])
|
||||
)
|
||||
return this.effects.resetProxyConfig()
|
||||
]),
|
||||
})
|
||||
|
||||
const { effects } = this
|
||||
await Promise.all([
|
||||
effects.resetChannel(),
|
||||
effects.resetProxyConfig(),
|
||||
effects.update(),
|
||||
])
|
||||
},
|
||||
initialize() {
|
||||
return this.effects.update()
|
||||
},
|
||||
linkState,
|
||||
onChannelChange: (_, channel) => ({ channel }),
|
||||
async register() {
|
||||
const { state } = this
|
||||
const {
|
||||
props: { xoaRegisterState },
|
||||
state,
|
||||
} = this
|
||||
|
||||
const { isRegistered } = state
|
||||
if (isRegistered) {
|
||||
@@ -114,7 +133,7 @@ const Updates = decorate([
|
||||
body: (
|
||||
<p>
|
||||
{_('alreadyRegisteredModalText', {
|
||||
email: this.props.xoaRegisterState.email,
|
||||
email: xoaRegisterState.email,
|
||||
})}
|
||||
</p>
|
||||
),
|
||||
@@ -128,11 +147,12 @@ const Updates = decorate([
|
||||
}
|
||||
|
||||
state.askRegisterAgain = false
|
||||
const { email, password } = state
|
||||
const { email = xoaRegisterState.email, password } = state
|
||||
await xoaUpdater.register(email, password, isRegistered)
|
||||
|
||||
return initialRegistrationState()
|
||||
},
|
||||
resetChannel: initialChannelState,
|
||||
resetProxyConfig: initialProxyState,
|
||||
async startTrial() {
|
||||
try {
|
||||
@@ -152,13 +172,41 @@ const Updates = decorate([
|
||||
},
|
||||
toggleState,
|
||||
update: () => xoaUpdater.update(),
|
||||
upgrade: () => xoaUpdater.upgrade(),
|
||||
upgrade() {
|
||||
return this.state.areJobsRunning
|
||||
? confirm({
|
||||
title: _('upgradeWarningTitle'),
|
||||
body: <p>{_('upgradeWarningMessage')}</p>,
|
||||
}).then(() => xoaUpdater.upgrade())
|
||||
: xoaUpdater.upgrade()
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
areJobsRunning: (_, { jobs, backupNgJobs }) =>
|
||||
jobs !== undefined &&
|
||||
backupNgJobs !== undefined &&
|
||||
some(jobs.concat(backupNgJobs), job => job.runId !== undefined),
|
||||
channelsFormId: generateId,
|
||||
channels: () => xoaUpdater.getReleaseChannels(),
|
||||
channelsOptions: ({ channels }) =>
|
||||
channels === undefined
|
||||
? undefined
|
||||
: [
|
||||
...Object.keys(channels)
|
||||
.sort()
|
||||
.map(channel => ({
|
||||
label: channel,
|
||||
value: channel,
|
||||
})),
|
||||
{
|
||||
label: (
|
||||
<span className='font-italic'>{_('unlistedChannel')}</span>
|
||||
),
|
||||
value: UNLISTED_CHANNEL_VALUE,
|
||||
},
|
||||
],
|
||||
consolidatedChannel: ({ channel }, { xoaConfiguration }) =>
|
||||
defined(channel, xoaConfiguration.channel),
|
||||
async installedPackages() {
|
||||
const { installer, updater, npm } = await xoaUpdater.getLocalManifest()
|
||||
return { ...installer, ...updater, ...npm }
|
||||
@@ -178,6 +226,8 @@ const Updates = decorate([
|
||||
xoaTrialState.state === 'default' &&
|
||||
!isTrialRunning(xoaTrialState.trial) &&
|
||||
!exposeTrial(xoaTrialState.trial),
|
||||
isUnlistedChannel: ({ consolidatedChannel, channels }) =>
|
||||
consolidatedChannel !== undefined && !(consolidatedChannel in channels),
|
||||
isUpdaterDown: (_, { xoaTrialState }) =>
|
||||
isEmpty(xoaTrialState) || xoaTrialState.state === 'ERROR',
|
||||
packagesList: ({ installedPackages }) =>
|
||||
@@ -202,7 +252,7 @@ const Updates = decorate([
|
||||
}) => (
|
||||
<Container>
|
||||
<Row>
|
||||
<Col mediumSize={12}>
|
||||
<Col mediumSize={6}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<UpdateTag /> {LABELS_BY_STATE[xoaUpdaterState]}
|
||||
@@ -245,7 +295,6 @@ const Updates = decorate([
|
||||
</ActionButton>{' '}
|
||||
<ActionButton
|
||||
btnStyle='success'
|
||||
data-runningJobsExist={state.areJobsRunning}
|
||||
disabled={
|
||||
xoaUpdaterState !== 'upgradeNeeded' &&
|
||||
xoaTrialState.state !== 'untrustedTrial'
|
||||
@@ -276,6 +325,56 @@ const Updates = decorate([
|
||||
</CardBlock>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col mediumSize={6}>
|
||||
<Card>
|
||||
<CardHeader>{_('releaseChannels')}</CardHeader>
|
||||
<CardBlock>
|
||||
<form id={state.channelsFormId} className='form'>
|
||||
<div className='form-group'>
|
||||
<Select
|
||||
isLoading={state.channelsOptions === undefined}
|
||||
onChange={effects.onChannelChange}
|
||||
options={state.channelsOptions}
|
||||
placeholder={formatMessage(messages.selectChannel)}
|
||||
required
|
||||
simpleValue
|
||||
value={
|
||||
state.isUnlistedChannel
|
||||
? UNLISTED_CHANNEL_VALUE
|
||||
: state.consolidatedChannel
|
||||
}
|
||||
/>
|
||||
<br />
|
||||
{state.isUnlistedChannel && (
|
||||
<div className='form-group'>
|
||||
<DebounceInput
|
||||
autoFocus
|
||||
className='form-control'
|
||||
debounceTimeout={500}
|
||||
name='channel'
|
||||
onChange={effects.linkState}
|
||||
placeholder={formatMessage(
|
||||
messages.unlistedChannelName
|
||||
)}
|
||||
required
|
||||
type='text'
|
||||
value={state.consolidatedChannel}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>{' '}
|
||||
<ActionButton
|
||||
btnStyle='primary'
|
||||
form={state.channelsFormId}
|
||||
handler={effects.configure}
|
||||
icon='success'
|
||||
>
|
||||
{_('changeChannel')}
|
||||
</ActionButton>
|
||||
</form>
|
||||
</CardBlock>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col mediumSize={6}>
|
||||
@@ -375,7 +474,7 @@ const Updates = decorate([
|
||||
</div>{' '}
|
||||
<div className='form-group'>
|
||||
<Password
|
||||
disabled={state.email === undefined}
|
||||
disabled={helper(state, xoaRegisterState, 'email') === ''}
|
||||
name='password'
|
||||
onChange={effects.linkState}
|
||||
placeholder={formatMessage(
|
||||
|
||||
@@ -14,6 +14,7 @@ import { every, filter, find, flatten, forEach, isEmpty, map } from 'lodash'
|
||||
import { get } from '@xen-orchestra/defined'
|
||||
import {
|
||||
addSubscriptions,
|
||||
adminOnly,
|
||||
connectStore,
|
||||
cowSet,
|
||||
formatSize,
|
||||
@@ -211,6 +212,7 @@ const XOSAN_INDIVIDUAL_ACTIONS = [
|
||||
},
|
||||
]
|
||||
|
||||
@adminOnly
|
||||
@connectStore(() => {
|
||||
const getHosts = createGetObjectsOfType('host')
|
||||
const getHostsByPool = getHosts.groupBy('$pool')
|
||||
|
||||
@@ -301,7 +301,7 @@ export default class NewXosan extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (process.env.XOA_PLAN === 5) {
|
||||
if (+process.env.XOA_PLAN === 5) {
|
||||
return (
|
||||
<em>
|
||||
{_('xosanSourcesDisclaimer', {
|
||||
|
||||
Reference in New Issue
Block a user