Compare commits

...

41 Commits

Author SHA1 Message Date
Julien Fontanet
4cf04aca72 feat(cr-seed-cli): 0.2.0 2018-10-04 15:09:22 +02:00
Julien Fontanet
410d6762bf fix(cr-seed-cli): set xo:backup:exported on snapshot
Related to bdefd0bcd
2018-10-04 15:08:16 +02:00
Julien Fontanet
0a95426e63 fix(xo-server/delta NG): detection of removed disks
Related to eb9655125
2018-10-04 14:53:33 +02:00
Julien Fontanet
4ec4970d49 chore(async-map): build for tests 2018-10-03 21:27:56 +02:00
Julien Fontanet
e57ae0a8ce chore(xo-server): add support for ?? 2018-10-03 16:00:24 +02:00
badrAZ
1e7852369f feat(xo-server/xapi-stats): expose VM IOPS metric (#3454) 2018-10-03 15:50:35 +02:00
Julien Fontanet
bdefd0bcd8 feat(xo-server/delta NG): mark successfully exported snapshots (#3485)
Allows runs after failure/interruption to be deltas.
Also, allows some schedules to only do rolling snapshots without breaking the exports.

Thanks a lot to @Samuel-BF for PR #3466.
2018-10-03 15:36:08 +02:00
badrAZ
fa88e1789c fix(xo-web/backup-ng/new): fix retention default value (#3486) 2018-10-03 15:26:48 +02:00
Pierre Donias
df90094cae feat(xo-web/pool/patches): extra modal before bulk install (#3484)
Fixes #3252
2018-10-03 15:15:04 +02:00
Pierre Donias
efdbc18a0a feat(xo-web/home/pool): show # of *unique* available patches (#3483)
Fixes #3321
2018-10-03 09:17:35 +02:00
Julien Fontanet
fc1dd3ce09 chore(xo-cli): got → http-request-plus 2018-10-02 20:19:43 +02:00
Julien Fontanet
10aff53d2c chore(xo-server-cloud): superagent → http-request-plus 2018-10-02 19:57:27 +02:00
Pierre Donias
85c3d64c04 feat(xo-web/host/network): private networks (#3481)
Fixes #3387
2018-10-02 14:42:24 +02:00
badrAZ
5a71ab53be feat(xo-acl-resolver): allow ACLs on VM snapshots (#3480)
Related to #3443
2018-10-02 13:33:26 +02:00
Julien Fontanet
d022b40732 chore: update dependencies 2018-10-02 12:01:14 +02:00
Pierre Donias
e105c0aad1 feat(xo-web/host/networks): remove "Add network" button (#3478)
Fixes #3386
2018-10-02 11:38:28 +02:00
Julien Fontanet
eb9655125c fix(xo-server/delta NG): handle removed disks (#3479) 2018-10-02 11:31:11 +02:00
Julien Fontanet
a10fea2823 chore(xo-server/xapi/utils): remove NULL_REF in favor of xen-api 2018-10-02 11:07:19 +02:00
badrAZ
0c05d89d3f fix(xo-web/VM/snapshot): allow VM admin to access snapshot tab (#3477) 2018-10-02 10:52:36 +02:00
Pierre Donias
d600d4cc28 chore(CHANGELOG): 5.27.1 2018-09-28 17:08:42 +02:00
Pierre Donias
4f0e5317ed feat(xo-web): 5.27.1 2018-09-28 17:05:40 +02:00
Pierre Donias
fce7c7fd49 feat(xo-server): 5.27.2 2018-09-28 17:04:48 +02:00
Pierre Donias
929ca767ca feat(xo-vmdk-to-vhd): 0.1.4 2018-09-28 17:01:43 +02:00
Pierre Donias
342c1bc6fa feat(vhd-lib): 0.3.1 2018-09-28 16:58:46 +02:00
Pierre Donias
317e301067 feat(fs): 0.3.1 2018-09-28 16:56:01 +02:00
Julien Fontanet
ac5741a341 fix(xo-server/deleteVm): build disks list before deleting VM (#3465)
Otherwise there is a race condition and VBD records might have been removed from cache before listing the VM disks due to destroying the VM.
2018-09-28 16:48:21 +02:00
badrAZ
3f1fb7092b fix(xo-web/modal#form): prevent default behavior of submit (#3462) 2018-09-28 11:42:47 +02:00
Julien Fontanet
7298a8b8f0 fix(fs/NfsHandler): race conds on sync() (#3460)
Fixes #3380
2018-09-28 11:40:12 +02:00
Julien Fontanet
856924c970 chore(log): remove @babel-polyfill dep 2018-09-26 17:41:43 +02:00
Julien Fontanet
9a285d280f chore(fs/NfsHandler): merge _loadRealMounts and _matchesRealMount in _sync 2018-09-26 17:00:47 +02:00
Julien Fontanet
9c3fc56d4a chore(fs/NfsHandler#_umount): remove unused remote param 2018-09-26 17:00:47 +02:00
Julien Fontanet
aaaee45eeb chore(xo-server): remove @babel/polyfill (#3453)
Fixes #2580
Fixes #2820
2018-09-26 14:22:02 +02:00
Nicolas Raynaud
ac2ab21826 fix(vhd-lib): fix geometry computation for more than 2^28 sectors (#3451)
Fixes https://gitlab.com/vates/xoa-support/issues/942
It was a bug in the disk geometry computation.
2018-09-25 14:51:17 +02:00
badrAZ
b22514646e feat(xo-web/backup-ng/overview): display the schedule's name (#3445)
Fixes #3444
2018-09-24 13:43:49 +02:00
Julien Fontanet
c7ab1ddb0c chore: update dependencies 2018-09-24 13:39:31 +02:00
Pierre Donias
7ddc595a1c fix(xo-web/file-restore): incorrect line-through on similar filenames (#3447) 2018-09-24 11:09:45 +02:00
Pierre Donias
4147800266 fix(xo-web/file-restore): ensure folder path trailing slash (#3446) 2018-09-24 10:52:48 +02:00
Pierre Donias
99e6d54647 chore(CHANGELOG): 5.27.0 2018-09-24 10:01:39 +02:00
Julien Fontanet
dac5901c6b feat(xo-server): 5.27.1 2018-09-22 14:01:24 +02:00
Julien Fontanet
308928a7d4 feat(xen-api): 0.19.0 2018-09-22 14:00:55 +02:00
Julien Fontanet
e6e3d2cd52 chore: update http-request-plus to 0.6.0 2018-09-22 13:06:30 +02:00
53 changed files with 3075 additions and 1154 deletions

View File

@@ -30,10 +30,10 @@
"lodash": "^4.17.4"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"
@@ -44,6 +44,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepare": "yarn run build",
"prepublishOnly": "yarn run build"
}
}

View File

@@ -83,6 +83,9 @@ ${cliName} v${pkg.version}
await Promise.all([
srcXapi.setFieldEntries(srcSnapshot, 'other_config', metadata),
srcXapi.setFieldEntries(srcSnapshot, 'other_config', {
'xo:backup:exported': 'true',
}),
tgtXapi.setField(
tgtVm,
'name_label',

View File

@@ -1,6 +1,6 @@
{
"name": "@xen-orchestra/cr-seed-cli",
"version": "0.1.0",
"version": "0.2.0",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/cr-seed-cli",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
@@ -15,6 +15,6 @@
},
"dependencies": {
"golike-defer": "^0.4.1",
"xen-api": "^0.18.0"
"xen-api": "^0.19.0"
}
}

View File

@@ -41,10 +41,10 @@
"moment-timezone": "^0.5.14"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"
},

View File

@@ -28,10 +28,10 @@
},
"dependencies": {},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"

View File

@@ -28,9 +28,9 @@
},
"dependencies": {},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"

View File

@@ -1,6 +1,6 @@
{
"name": "@xen-orchestra/fs",
"version": "0.3.0",
"version": "0.3.1",
"license": "AGPL-3.0",
"description": "The File System for Xen Orchestra backups.",
"keywords": [],
@@ -31,11 +31,11 @@
"xo-remote-parser": "^0.5.0"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/plugin-proposal-function-bind": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-function-bind": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"index-modules": "^0.3.0",

View File

@@ -1,6 +1,5 @@
import execa from 'execa'
import fs from 'fs-extra'
import { forEach } from 'lodash'
import LocalHandler from './local'
@@ -15,63 +14,38 @@ export default class NfsHandler extends LocalHandler {
return `/run/xo-server/mounts/${this._remote.id}`
}
async _loadRealMounts () {
let stdout
const mounted = {}
try {
stdout = await execa.stdout('findmnt', [
'-P',
'-t',
'nfs,nfs4',
'--output',
'SOURCE,TARGET',
'--noheadings',
])
const regex = /^SOURCE="([^:]*):(.*)" TARGET="(.*)"$/
forEach(stdout.split('\n'), m => {
if (m) {
const match = regex.exec(m)
mounted[match[3]] = {
host: match[1],
share: match[2],
}
}
})
} catch (exc) {
// When no mounts are found, the call pretends to fail...
if (exc.stderr !== '') {
throw exc
}
}
this._realMounts = mounted
return mounted
}
_matchesRealMount () {
return this._getRealPath() in this._realMounts
}
async _mount () {
await fs.ensureDir(this._getRealPath())
const { host, path, port, options } = this._remote
return execa('mount', [
'-t',
'nfs',
'-o',
DEFAULT_NFS_OPTIONS + (options !== undefined ? `,${options}` : ''),
`${host}${port !== undefined ? ':' + port : ''}:${path}`,
this._getRealPath(),
])
return execa(
'mount',
[
'-t',
'nfs',
'-o',
DEFAULT_NFS_OPTIONS + (options !== undefined ? `,${options}` : ''),
`${host}${port !== undefined ? ':' + port : ''}:${path}`,
this._getRealPath(),
],
{
env: {
LANG: 'C',
},
}
).catch(error => {
if (!error.stderr.includes('already mounted')) {
throw error
}
})
}
async _sync () {
await this._loadRealMounts()
if (this._matchesRealMount() && !this._remote.enabled) {
await this._umount(this._remote)
} else if (!this._matchesRealMount() && this._remote.enabled) {
if (this._remote.enabled) {
await this._mount()
} else {
await this._umount()
}
return this._remote
}
@@ -83,7 +57,15 @@ export default class NfsHandler extends LocalHandler {
}
}
async _umount (remote) {
await execa('umount', ['--force', this._getRealPath()])
async _umount () {
await execa('umount', ['--force', this._getRealPath()], {
env: {
LANG: 'C',
},
}).catch(error => {
if (!error.stderr.includes('not mounted')) {
throw error
}
})
}
}

View File

@@ -28,14 +28,13 @@
"node": ">=4"
},
"dependencies": {
"@babel/polyfill": "7.0.0",
"lodash": "^4.17.4",
"promise-toolbox": "^0.10.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"index-modules": "^0.3.0",

View File

@@ -30,9 +30,9 @@
"bind-property-descriptor": "^1.0.0"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-dev": "^1.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",

View File

@@ -4,6 +4,47 @@
### Enhancements
- [Host/Networks] Remove "Add network" button [#3386](https://github.com/vatesfr/xen-orchestra/issues/3386) (PR [#3478](https://github.com/vatesfr/xen-orchestra/pull/3478))
- [Host/networks] Private networks table [#3387](https://github.com/vatesfr/xen-orchestra/issues/3387) (PR [#3481](https://github.com/vatesfr/xen-orchestra/pull/3481))
- [Home/pool] Patch count pill now shows the number of unique patches in the pool [#3321](https://github.com/vatesfr/xen-orchestra/issues/3321) (PR [#3483](https://github.com/vatesfr/xen-orchestra/pull/3483))
- [Patches] Pre-install checks to avoid errors [#3252](https://github.com/vatesfr/xen-orchestra/issues/3252)
### Bug fixes
- [Backup NG] Fix `Cannot read property 'uuid' of undefined` when a disk is removed from a VM to backup (PR [#3479](https://github.com/vatesfr/xen-orchestra/pull/3479))
- [Backup NG] Fix unexpected full after failure, interruption or basic rolling snapshot (PR [#3485](https://github.com/vatesfr/xen-orchestra/pull/3485))
### Released packages
- xo-acl-resolver v0.3.0
- xo-server v5.28.0
- xo-web v5.28.0
## **5.27.1** (2018-09-28)
### Enhancements
### Bug fixes
- [OVA Import] Allow import of files bigger than 127GB (PR [#3451](https://github.com/vatesfr/xen-orchestra/pull/3451))
- [File restore] Fix a path issue when going back to the parent folder (PR [#3446](https://github.com/vatesfr/xen-orchestra/pull/3446))
- [File restore] Fix a minor issue when showing which selected files are redundant (PR [#3447](https://github.com/vatesfr/xen-orchestra/pull/3447))
- [Memory] Fix a major leak [#2580](https://github.com/vatesfr/xen-orchestra/issues/2580) [#2820](https://github.com/vatesfr/xen-orchestra/issues/2820) (PR [#3453](https://github.com/vatesfr/xen-orchestra/pull/3453))
- [NFS Remotes] Fix `already mounted` race condition [#3380](https://github.com/vatesfr/xen-orchestra/issues/3380) (PR [#3460](https://github.com/vatesfr/xen-orchestra/pull/3460))
- Fix `Cannot read property 'type' of undefined` when deleting a VM (PR [#3465](https://github.com/vatesfr/xen-orchestra/pull/3465))
### Released packages
- @xen-orchestra/fs v0.3.1
- vhd-lib v0.3.1
- xo-vmdk-to-vhd v0.1.4
- xo-server v5.27.2
- xo-web v5.27.1
## **5.27.0** (2018-09-24)
### Enhancements
- [Remotes] Test the remote automatically on changes [#3323](https://github.com/vatesfr/xen-orchestra/issues/3323) (PR [#3397](https://github.com/vatesfr/xen-orchestra/pull/3397))
- [Remotes] Use *WORKGROUP* as default domain for new SMB remote (PR [#3398](https://github.com/vatesfr/xen-orchestra/pull/3398))
- [Backup NG form] Display a tip to encourage users to create vms on a thin-provisioned storage [#3334](https://github.com/vatesfr/xen-orchestra/issues/3334) (PR [#3402](https://github.com/vatesfr/xen-orchestra/pull/3402))
@@ -13,6 +54,7 @@
- [Backup reports] Ability to test the plugin (PR [#3421](https://github.com/vatesfr/xen-orchestra/pull/3421))
- [Backup NG] Ability to restart failed VMs' backup [#3339](https://github.com/vatesfr/xen-orchestra/issues/3339) (PR [#3420](https://github.com/vatesfr/xen-orchestra/pull/3420))
- [VM] Ability to change the NIC type [#3423](https://github.com/vatesfr/xen-orchestra/issues/3423) (PR [#3440](https://github.com/vatesfr/xen-orchestra/pull/3440))
- [Backup NG Overview] Display the schedule's name [#3444](https://github.com/vatesfr/xen-orchestra/issues/3444) (PR [#3445](https://github.com/vatesfr/xen-orchestra/pull/3445))
### Bug fixes
@@ -24,6 +66,7 @@
- [Backup NG] Fix issue when *Delete first* was enabled for some of the remotes [#3424](https://github.com/vatesfr/xen-orchestra/issues/3424) (PR [#3427](https://github.com/vatesfr/xen-orchestra/pull/3427))
- [VM/host consoles] Work around a XenServer/XCP-ng issue which lead to some consoles not working [#3432](https://github.com/vatesfr/xen-orchestra/issues/3432) (PR [#3435](https://github.com/vatesfr/xen-orchestra/pull/3435))
- [Backup NG] Remove extraneous snapshots in case of multiple schedules [#3132](https://github.com/vatesfr/xen-orchestra/issues/3132) (PR [#3439](https://github.com/vatesfr/xen-orchestra/pull/3439))
- [Backup NG] Fix page reloaded on creating a schedule [#3461](https://github.com/vatesfr/xen-orchestra/issues/3461) (PR [#3462](https://github.com/vatesfr/xen-orchestra/pull/3462))
### Released packages

View File

@@ -1,9 +1,9 @@
{
"devDependencies": {
"@babel/core": "7.0.0",
"@babel/register": "7.0.0",
"@babel/core": "^7.0.0",
"@babel/register": "^7.0.0",
"babel-core": "^7.0.0-0",
"babel-eslint": "^9.0.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^23.0.1",
"benchmark": "^2.1.4",
"eslint": "^5.1.0",
@@ -15,9 +15,9 @@
"eslint-plugin-react": "^7.6.1",
"eslint-plugin-standard": "^4.0.0",
"exec-promise": "^0.7.0",
"flow-bin": "^0.80.0",
"flow-bin": "^0.82.0",
"globby": "^8.0.0",
"husky": "^0.14.3",
"husky": "^1.0.0-rc.15",
"jest": "^23.0.1",
"lodash": "^4.17.4",
"prettier": "^1.10.2",
@@ -27,6 +27,11 @@
"engines": {
"yarn": "^1.7.0"
},
"husky": {
"hooks": {
"pre-commit": "scripts/lint-staged"
}
},
"jest": {
"collectCoverage": true,
"projects": [
@@ -49,7 +54,6 @@
"dev": "scripts/run-script --parallel dev",
"dev-test": "jest --bail --watch \"^(?!.*\\.integ\\.spec\\.js$)\"",
"posttest": "scripts/run-script test",
"precommit": "scripts/lint-staged",
"prepare": "scripts/run-script prepare",
"pretest": "eslint --ignore-path .gitignore .",
"test": "jest \"^(?!.*\\.integ\\.spec\\.js$)\"",

View File

@@ -30,9 +30,9 @@
"lodash": "^4.17.4"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.1",
"rimraf": "^2.6.2"

View File

@@ -28,10 +28,10 @@
},
"dependencies": {},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"
},

View File

@@ -33,9 +33,9 @@
"vhd-lib": "^0.3.0"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"execa": "^1.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "vhd-lib",
"version": "0.3.0",
"version": "0.3.1",
"license": "AGPL-3.0",
"description": "Primitives for VHD file handling",
"keywords": [],
@@ -29,11 +29,11 @@
"uuid": "^3.0.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@xen-orchestra/fs": "^0.3.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"@xen-orchestra/fs": "^0.3.1",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"execa": "^1.0.0",

View File

@@ -1,13 +1,10 @@
import { SECTOR_SIZE } from './_constants'
export default function computeGeometryForSize (size) {
const totalSectors = Math.ceil(size / 512)
const totalSectors = Math.min(Math.ceil(size / 512), 65535 * 16 * 255)
let sectorsPerTrackCylinder
let heads
let cylinderTimesHeads
if (totalSectors > 65535 * 16 * 255) {
throw Error('disk is too big')
}
// straight copypasta from the file spec appendix on CHS Calculation
if (totalSectors >= 65535 * 16 * 63) {
sectorsPerTrackCylinder = 255

View File

@@ -1,6 +1,6 @@
{
"name": "xen-api",
"version": "0.18.0",
"version": "0.19.0",
"license": "ISC",
"description": "Connector to the Xen API",
"keywords": [
@@ -33,10 +33,10 @@
},
"dependencies": {
"blocked": "^1.2.1",
"debug": "^3.1.0",
"debug": "^4.0.1",
"event-to-promise": "^0.8.0",
"exec-promise": "^0.7.0",
"http-request-plus": "^0.5.0",
"http-request-plus": "^0.6.0",
"iterable-backoff": "^0.0.0",
"jest-diff": "^23.5.0",
"json-rpc-protocol": "^0.12.0",
@@ -51,10 +51,10 @@
"xo-collection": "^0.4.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/plugin-proposal-decorators": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.1"

View File

@@ -86,7 +86,7 @@ const checkAuthorizationByTypes = {
'VM-controller': checkMember('$container'),
'VM-snapshot': checkMember('$snapshot_of'),
'VM-snapshot': or(checkSelf, checkMember('$snapshot_of')),
'VM-template': or(checkSelf, checkMember('$pool')),
}

View File

@@ -28,31 +28,32 @@
"node": ">=6"
},
"dependencies": {
"@babel/polyfill": "7.0.0",
"@babel/polyfill": "^7.0.0",
"bluebird": "^3.5.1",
"chalk": "^2.2.0",
"event-to-promise": "^0.8.0",
"exec-promise": "^0.7.0",
"fs-promise": "^2.0.3",
"got": "^8.0.1",
"http-request-plus": "^0.6.0",
"human-format": "^0.10.0",
"l33teral": "^3.0.3",
"lodash": "^4.17.4",
"micromatch": "^3.1.3",
"mkdirp": "^0.5.1",
"nice-pipe": "0.0.0",
"pretty-ms": "^3.0.1",
"pretty-ms": "^4.0.0",
"progress-stream": "^2.0.0",
"promise-toolbox": "^0.10.1",
"pump": "^3.0.0",
"pw": "^0.0.4",
"strip-indent": "^2.0.0",
"xdg-basedir": "^3.0.0",
"xo-lib": "^0.9.0"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"

View File

@@ -11,10 +11,10 @@ const resolveUrl = require('url').resolve
const stat = require('fs-promise').stat
const chalk = require('chalk')
const eventToPromise = require('event-to-promise')
const forEach = require('lodash/forEach')
const fromCallback = require('promise-toolbox/fromCallback')
const getKeys = require('lodash/keys')
const got = require('got')
const hrp = require('http-request-plus').default
const humanFormat = require('human-format')
const identity = require('lodash/identity')
const isArray = require('lodash/isArray')
@@ -23,6 +23,7 @@ const micromatch = require('micromatch')
const nicePipe = require('nice-pipe')
const pairs = require('lodash/toPairs')
const pick = require('lodash/pick')
const pump = require('pump')
const startsWith = require('lodash/startsWith')
const prettyMs = require('pretty-ms')
const progressStream = require('progress-stream')
@@ -362,50 +363,43 @@ async function call (args) {
ensurePathParam(method, file)
url = resolveUrl(baseUrl, result[key])
const output = createWriteStream(file)
const response = await hrp(url)
const progress = progressStream({ time: 1e3 }, printProgress)
return eventToPromise(
nicePipe([
got.stream(url).on('response', function (response) {
const length = response.headers['content-length']
if (length !== undefined) {
progress.length(length)
}
}),
progress,
output,
]),
'finish'
const progress = progressStream(
{
length: response.headers['content-length'],
time: 1e3,
},
printProgress
)
return fromCallback(cb => pump(response, progress, output, cb))
}
if (key === '$sendTo') {
ensurePathParam(method, file)
url = resolveUrl(baseUrl, result[key])
const stats = await stat(file)
const length = stats.size
const { size: length } = await stat(file)
const input = nicePipe([
createReadStream(file),
progressStream(
{
length: length,
length,
time: 1e3,
},
printProgress
),
])
const response = await got.post(url, {
body: input,
headers: {
'content-length': length,
},
method: 'POST',
})
return response.body
return hrp
.post(url, {
body: input,
headers: {
'content-length': length,
},
})
.readAll('utf-8')
}
}

View File

@@ -30,9 +30,9 @@
"make-error": "^1.0.2"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"cross-env": "^5.1.3",
"event-to-promise": "^0.8.0",
"rimraf": "^2.6.1"

View File

@@ -27,9 +27,9 @@
"lodash": "^4.13.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"deep-freeze": "^0.0.1",

View File

@@ -41,9 +41,9 @@
"promise-toolbox": "^0.10.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.1"

View File

@@ -40,9 +40,9 @@
"moment-timezone": "^0.5.13"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.1"

View File

@@ -31,14 +31,13 @@
"node": ">=6"
},
"dependencies": {
"event-to-promise": "^0.8.0",
"jsonrpc-websocket-client": "^0.4.1",
"superagent": "^3.8.2"
"http-request-plus": "^0.6.0",
"jsonrpc-websocket-client": "^0.4.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"cross-env": "^5.1.3",
"rimraf": "^2.5.4"
},

View File

@@ -1,7 +1,5 @@
import Client, { createBackoff } from 'jsonrpc-websocket-client'
import eventToPromise from 'event-to-promise'
import request from 'superagent'
import { PassThrough } from 'stream'
import hrp from 'http-request-plus'
const UPDATER_URL = 'localhost'
const WS_PORT = 9001
@@ -145,17 +143,16 @@ class XoServerCloud {
throw new Error('cannot get download token')
}
const req = request
.get(`${UPDATER_URL}:${HTTP_PORT}/`)
.set('Authorization', `Bearer ${downloadToken}`)
const response = await hrp(`${UPDATER_URL}:${HTTP_PORT}/`, {
headers: {
Authorization: `Bearer ${downloadToken}`,
},
})
// Impossible to pipe the response directly: https://github.com/visionmedia/superagent/issues/1187
const pt = new PassThrough()
req.pipe(pt)
const { headers } = await eventToPromise(req, 'response')
pt.length = headers['content-length']
// currently needed for XenApi#putResource()
response.length = response.headers['content-length']
return pt
return response
}
}

View File

@@ -34,9 +34,9 @@
"lodash": "^4.16.2"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2"
},
"scripts": {

View File

@@ -26,10 +26,10 @@
"lodash": "^4.17.4"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"

View File

@@ -41,9 +41,9 @@
"promise-toolbox": "^0.10.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.1"

View File

@@ -1,6 +1,6 @@
{
"name": "xo-server",
"version": "5.27.0",
"version": "5.27.2",
"license": "AGPL-3.0",
"description": "Server part of Xen-Orchestra",
"keywords": [
@@ -31,11 +31,10 @@
"node": ">=6"
},
"dependencies": {
"@babel/polyfill": "7.0.0",
"@xen-orchestra/async-map": "^0.0.0",
"@xen-orchestra/cron": "^1.0.3",
"@xen-orchestra/emit-async": "^0.0.0",
"@xen-orchestra/fs": "^0.3.0",
"@xen-orchestra/fs": "^0.3.1",
"@xen-orchestra/mixin": "^0.0.0",
"ajv": "^6.1.1",
"app-conf": "^0.5.0",
@@ -49,7 +48,7 @@
"cookie": "^0.3.1",
"cookie-parser": "^1.4.3",
"d3-time-format": "^2.1.1",
"debug": "^3.1.0",
"debug": "^4.0.1",
"decorator-synchronized": "^0.3.0",
"deptree": "^1.0.0",
"escape-string-regexp": "^1.0.5",
@@ -67,7 +66,7 @@
"helmet": "^3.9.0",
"highland": "^2.11.1",
"http-proxy": "^1.16.2",
"http-request-plus": "^0.5.0",
"http-request-plus": "^0.6.0",
"http-server-plus": "^0.10.0",
"human-format": "^0.10.0",
"is-redirect": "^1.0.0",
@@ -113,29 +112,30 @@
"tmp": "^0.0.33",
"uuid": "^3.0.1",
"value-matcher": "^0.2.0",
"vhd-lib": "^0.3.0",
"vhd-lib": "^0.3.1",
"ws": "^6.0.0",
"xen-api": "^0.18.0",
"xen-api": "^0.19.0",
"xml2js": "^0.4.19",
"xo-acl-resolver": "^0.2.4",
"xo-collection": "^0.4.1",
"xo-common": "^0.1.1",
"xo-remote-parser": "^0.5.0",
"xo-vmdk-to-vhd": "^0.1.3",
"xo-vmdk-to-vhd": "^0.1.4",
"yazl": "^2.4.3"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/plugin-proposal-decorators": "7.0.0",
"@babel/plugin-proposal-export-default-from": "7.0.0",
"@babel/plugin-proposal-export-namespace-from": "7.0.0",
"@babel/plugin-proposal-function-bind": "7.0.0",
"@babel/plugin-proposal-optional-chaining": "7.0.0",
"@babel/plugin-proposal-pipeline-operator": "7.0.0",
"@babel/plugin-proposal-throw-expressions": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.0.0",
"@babel/plugin-proposal-export-default-from": "^7.0.0",
"@babel/plugin-proposal-export-namespace-from": "^7.0.0",
"@babel/plugin-proposal-function-bind": "^7.0.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"index-modules": "^0.3.0",

View File

@@ -1,4 +1,5 @@
import appConf from 'app-conf'
import assert from 'assert'
import bind from 'lodash/bind'
import blocked from 'blocked'
import createExpress from 'express'
@@ -540,6 +541,9 @@ ${name} v${version}`)(require('../package.json'))
// ===================================================================
export default async function main (args) {
// makes sure the global Promise has not been changed by a lib
assert(global.Promise === require('bluebird'))
if (includes(args, '--help') || includes(args, '-h')) {
return USAGE
}

View File

@@ -230,6 +230,16 @@ const STATS = {
getPath: matches => ['xvds', 'w', matches[1]],
},
},
iops: {
r: {
test: /^vbd_xvd(.)_iops_read$/,
getPath: matches => ['iops', 'r', matches[1]],
},
w: {
test: /^vbd_xvd(.)_iops_write$/,
getPath: matches => ['iops', 'w', matches[1]],
},
},
},
}

View File

@@ -16,7 +16,7 @@ import {
} from 'promise-toolbox'
import { PassThrough } from 'stream'
import { forbiddenOperation } from 'xo-common/api-errors'
import { Xapi as XapiBase } from 'xen-api'
import { Xapi as XapiBase, NULL_REF } from 'xen-api'
import {
every,
find,
@@ -63,7 +63,6 @@ import {
canSrHaveNewVdiOfSize,
isVmHvm,
isVmRunning,
NULL_REF,
optional,
parseDateTime,
prepareXapiParam,
@@ -674,6 +673,9 @@ export default class Xapi extends XapiBase {
})
}
// must be done before destroying the VM
const disks = getVmDisks(vm)
// this cannot be done in parallel, otherwise disks and snapshots will be
// destroyed even if this fails
await this.call('VM.destroy', $ref)
@@ -684,7 +686,7 @@ export default class Xapi extends XapiBase {
)::ignoreErrors(),
deleteDisks &&
asyncMap(getVmDisks(vm), ({ $ref: vdiRef }) => {
asyncMap(disks, ({ $ref: vdiRef }) => {
let onFailure = () => {
onFailure = vdi => {
console.error(

View File

@@ -1,10 +1,11 @@
import deferrable from 'golike-defer'
import { ignoreErrors, pCatch } from 'promise-toolbox'
import { find, gte, includes, isEmpty, lte, noop } from 'lodash'
import { ignoreErrors, pCatch } from 'promise-toolbox'
import { NULL_REF } from 'xen-api'
import { forEach, mapToArray, parseSize } from '../../utils'
import { isVmHvm, isVmRunning, makeEditObject, NULL_REF } from '../utils'
import { isVmHvm, isVmRunning, makeEditObject } from '../utils'
// According to: https://xenserver.org/blog/entry/vga-over-cirrus-in-xenserver-6-2.html.
const XEN_VGA_VALUES = ['std', 'cirrus']

View File

@@ -1,4 +1,4 @@
import { NULL_REF } from './utils'
import { NULL_REF } from 'xen-api'
const OTHER_CONFIG_TEMPLATE = {
actions_after_crash: 'restart',

View File

@@ -366,10 +366,6 @@ export const makeEditObject = specs => {
// ===================================================================
export const NULL_REF = 'OpaqueRef:NULL'
// ===================================================================
export const useUpdateSystem = host => {
// Match Xen Center's condition: https://github.com/xenserver/xenadmin/blob/f3a64fc54bbff239ca6f285406d9034f57537d64/XenModel/Utils/Helpers.cs#L420
return versionSatisfies(host.software_version.platform_version, '^2.1.1')

View File

@@ -11,8 +11,9 @@ import { AssertionError } from 'assert'
import { basename, dirname } from 'path'
import {
countBy,
findLast,
flatMap,
forEach,
forOwn,
groupBy,
isEmpty,
last,
@@ -769,6 +770,7 @@ export default class BackupNg {
},
xapi._updateObjectMapProperty(vm, 'other_config', {
'xo:backup:datetime': null,
'xo:backup:exported': null,
'xo:backup:job': null,
'xo:backup:schedule': null,
'xo:backup:vm': null,
@@ -889,7 +891,19 @@ export default class BackupNg {
snapshot = await xapi.barrier(snapshot.$ref)
let baseSnapshot = mode === 'delta' ? last(snapshots) : undefined
let baseSnapshot
if (mode === 'delta') {
baseSnapshot = findLast(
snapshots,
_ => 'xo:backup:exported' in _.other_config
)
// JFT 2018-10-02: support previous snapshots which did not have this
// entry, can be removed after 2018-12.
if (baseSnapshot === undefined) {
baseSnapshot = last(snapshots)
}
}
snapshots.push(snapshot)
// snapshots to delete due to the snapshot retention settings
@@ -1135,6 +1149,15 @@ export default class BackupNg {
const fullRequired = { __proto__: null }
const vdis: $Dict<Vdi> = getVmDisks(baseSnapshot)
// ignore VDI snapshots which no longer have a parent
forOwn(vdis, (vdi, key, vdis) => {
// `vdi.snapshot_of` is not always set to the null ref, it can contain
// an invalid ref, that's why the test is on `vdi.$snapshot_of`
if (vdi.$snapshot_of === undefined) {
delete vdis[key]
}
})
for (const { $id: srId, xapi } of srs) {
const replicatedVm = listReplicatedVms(
xapi,
@@ -1151,7 +1174,7 @@ export default class BackupNg {
getVmDisks(replicatedVm),
vdi => vdi.other_config[TAG_COPY_SRC]
)
forEach(vdis, vdi => {
forOwn(vdis, vdi => {
if (!(vdi.uuid in replicatedVdis)) {
fullRequired[vdi.$snapshot_of.$id] = true
}
@@ -1439,6 +1462,17 @@ export default class BackupNg {
} else {
throw new Error(`no exporter for backup mode ${mode}`)
}
await wrapTask(
{
logger,
message: 'set snapshot.other_config[xo:backup:exported]',
parentId: taskId,
},
xapi._updateObjectMapProperty(snapshot, 'other_config', {
'xo:backup:exported': 'true',
})
)
}
async _deleteDeltaVmBackups (

View File

@@ -1,6 +1,6 @@
{
"name": "xo-vmdk-to-vhd",
"version": "0.1.3",
"version": "0.1.4",
"license": "AGPL-3.0",
"description": "JS lib streaming a vmdk file to a vhd",
"keywords": [
@@ -27,12 +27,12 @@
"pipette": "^0.9.3",
"promise-toolbox": "^0.10.1",
"tmp": "^0.0.33",
"vhd-lib": "^0.3.0"
"vhd-lib": "^0.3.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"event-to-promise": "^0.8.0",

View File

@@ -1,7 +1,7 @@
{
"private": false,
"name": "xo-web",
"version": "5.27.0",
"version": "5.27.1",
"license": "AGPL-3.0",
"description": "Web interface client for Xen-Orchestra",
"keywords": [
@@ -139,7 +139,7 @@
"xo-common": "^0.1.1",
"xo-lib": "^0.8.0",
"xo-remote-parser": "^0.5.0",
"xo-vmdk-to-vhd": "^0.1.3"
"xo-vmdk-to-vhd": "^0.1.4"
},
"scripts": {
"build": "NODE_ENV=production gulp build",

View File

@@ -676,6 +676,8 @@ const messages = {
poolNetworkNameLabel: 'Name',
poolNetworkDescription: 'Description',
poolNetworkPif: 'PIFs',
privateNetworks: 'Private networks',
manage: 'Manage',
poolNoNetwork: 'No networks',
poolNetworkMTU: 'MTU',
poolNetworkPifAttached: 'Connected',
@@ -829,9 +831,17 @@ const messages = {
// ----- Pool patch tabs -----
refreshPatches: 'Refresh patches',
installPoolPatches: 'Install pool patches',
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',
// ----- Pool storage tabs -----
defaultSr: 'Default SR',
setAsDefaultSr: 'Set as default SR',
setDefaultSr: 'Set default SR:',
// ----- VM tabs -----
generalTabName: 'General',

View File

@@ -259,6 +259,8 @@ export const confirm = ({ body, icon = 'alarm', title, strongConfirm }) =>
// -----------------------------------------------------------------------------
const preventDefault = event => event.preventDefault()
class FormModal extends BaseComponent {
state = {
value: this.props.defaultValue,
@@ -271,7 +273,7 @@ class FormModal extends BaseComponent {
render () {
const { body, formId } = this.props
return (
<form id={formId}>
<form id={formId} onSubmit={preventDefault}>
{cloneElement(body, {
value: this.state.value,
onChange: this.linkState('value'),

View File

@@ -715,10 +715,21 @@ export const installAllHostPatches = host =>
subscribeHostMissingPatches.forceRefresh(host)
)
export const installAllPatchesOnPool = pool =>
_call('pool.installAllPatches', { pool: resolveId(pool) })::tap(() =>
subscribeHostMissingPatches.forceRefresh()
import InstallPoolPatchesModalBody from './install-pool-patches-modal' // eslint-disable-line import/first
export const installAllPatchesOnPool = pool => {
const poolId = resolveId(pool)
return confirm({
body: <InstallPoolPatchesModalBody pool={poolId} />,
title: _('installPoolPatches'),
icon: 'host-patch-update',
}).then(
() =>
_call('pool.installAllPatches', { pool: poolId })::tap(() =>
subscribeHostMissingPatches.forceRefresh()
),
noop
)
}
export const installSupplementalPack = (host, file) => {
info(

View File

@@ -0,0 +1,124 @@
import _ from 'intl'
import ActionButton from 'action-button'
import Component from 'base-component'
import Icon from 'icon'
import React from 'react'
import SingleLineRow from 'single-line-row'
import Tooltip from 'tooltip'
import { Container, Col } from 'grid'
import { connectStore } from 'utils'
import {
createCollectionWrapper,
createCounter,
createGetObject,
createGetObjectsOfType,
createSelector,
} from 'selectors'
import { forEach } from 'lodash'
import { SelectSr } from 'select-objects'
import { VmItem } from 'render-xo-item'
import { ejectCd, isSrWritable, setDefaultSr } from 'xo'
@connectStore(() => {
const getPool = createGetObject((_, props) => props.pool)
return {
pool: getPool,
poolMaster: createGetObject(
createSelector(getPool, ({ master }) => master)
),
vbds: createGetObjectsOfType('VBD').filter(
createSelector(getPool, ({ id }) => vbd => vbd.$pool === id)
),
}
})
export default class InstallPoolPatchesModalBody extends Component {
_getVmsWithCds = createSelector(
() => this.props.vbds,
createCollectionWrapper(vbds => {
const vmIds = []
forEach(vbds, vbd => {
if (
vbd.is_cd_drive &&
vbd.VDI !== undefined &&
!vmIds.includes(vbd.VM)
) {
vmIds.push(vbd.VM)
}
})
return vmIds
})
)
_getNVmsWithCds = createCounter(this._getVmsWithCds)
_ejectCds = () => Promise.all(this._getVmsWithCds().map(ejectCd))
_getTooltip = createSelector(this._getVmsWithCds, vmIds =>
vmIds.map(vmId => (
<p className='m-0' key={vmId}>
<VmItem id={vmId} />
</p>
))
)
render () {
const { pool, poolMaster } = this.props
const needDefaultSr =
poolMaster.productBrand !== 'XCP-ng' && pool.default_SR === undefined
const someCdsInserted = this._getNVmsWithCds() > 0
return (
<Container>
{!needDefaultSr &&
!someCdsInserted && (
<SingleLineRow>
<Col>{_('confirmPoolPatch')}</Col>
</SingleLineRow>
)}
{needDefaultSr && [
<SingleLineRow className='mt-1' key='message'>
<Col>
<Icon icon='alarm' /> {_('poolNeedsDefaultSr')}
</Col>
</SingleLineRow>,
<SingleLineRow className='mt-1' key='select'>
<Col size={6}>{_('setDefaultSr')}</Col>
<Col size={5}>
<div className='input-group'>
<SelectSr
onChange={this.linkState('sr')}
predicate={sr => sr.$pool === pool.id && isSrWritable(sr)}
value={this.state.sr}
/>
<span className='input-group-btn'>
<ActionButton
handler={setDefaultSr}
handlerParam={this.state.sr}
icon='save'
>
{_('formOk')}
</ActionButton>
</span>
</div>
</Col>
</SingleLineRow>,
]}
{someCdsInserted && (
<SingleLineRow className='mt-1'>
<Tooltip content={this._getTooltip()}>
<Col size={6}>
<Icon icon='alarm' />{' '}
{_('vmsHaveCds', { nVms: this._getNVmsWithCds() })}
</Col>
</Tooltip>
<Col size={6}>
<ActionButton icon='vm-eject' handler={this._ejectCds}>
{_('ejectCds')}
</ActionButton>
</Col>
</SingleLineRow>
)}
</Container>
)
}
}

View File

@@ -46,6 +46,8 @@ const fileOptionRenderer = ({ isFile, name }) => (
</span>
)
const ensureTrailingSlash = path => path + (endsWith(path, '/') ? '' : '/')
// -----------------------------------------------------------------------------
const formatFilesOptions = (rawFiles, path) => {
@@ -56,7 +58,7 @@ const formatFilesOptions = (rawFiles, path) => {
id: '..',
isFile: false,
name: '..',
path: dirname(path),
path: ensureTrailingSlash(dirname(path)),
},
]
: []
@@ -258,8 +260,10 @@ export default class RestoreFileModalBody extends Component {
const redundantFiles = {}
forEach(files, file => {
redundantFiles[file.path] =
find(files, f => f !== file && startsWith(file.path, f.path)) !==
undefined
find(
files,
f => !f.isFile && f !== file && startsWith(file.path, f.path)
) !== undefined
})
return redundantFiles
}

View File

@@ -55,6 +55,7 @@ const _runBackupNgJob = ({ id, name, schedule }) =>
const SchedulePreviewBody = ({ item: job, userData: { schedulesByJob } }) => (
<table>
<tr className='text-muted'>
<th>{_('scheduleName')}</th>
<th>{_('scheduleCron')}</th>
<th>{_('scheduleTimezone')}</th>
<th>{_('scheduleExportRetention')}</th>
@@ -64,6 +65,7 @@ const SchedulePreviewBody = ({ item: job, userData: { schedulesByJob } }) => (
</tr>
{map(schedulesByJob && schedulesByJob[job.id], schedule => (
<tr key={schedule.id}>
<td>{schedule.name}</td>
<td>{schedule.cron}</td>
<td>{schedule.timezone}</td>
<td>{job.settings[schedule.id].exportRetention}</td>

View File

@@ -590,10 +590,18 @@ export default [
setting.snapshotRetention
)
? {
copyRetention: setting.copyRetention || DEFAULT_RETENTION,
exportRetention: setting.exportRetention || DEFAULT_RETENTION,
snapshotRetention:
setting.snapshotRetention || DEFAULT_RETENTION,
copyRetention: defined(
setting.copyRetention,
DEFAULT_RETENTION
),
exportRetention: defined(
setting.exportRetention,
DEFAULT_RETENTION
),
snapshotRetention: defined(
setting.snapshotRetention,
DEFAULT_RETENTION
),
}
: setting
),

View File

@@ -1,12 +1,9 @@
import _ from 'intl'
import Component from 'base-component'
import Ellipsis, { EllipsisContainer } from 'ellipsis'
import flatMap from 'lodash/flatMap'
import Icon from 'icon'
import map from 'lodash/map'
import React from 'react'
import SingleLineRow from 'single-line-row'
import size from 'lodash/size'
import HomeTags from 'home-tags'
import Tooltip from 'tooltip'
import Link, { BlockLink } from 'link'
@@ -14,6 +11,7 @@ import { Col } from 'grid'
import { Text } from 'editable'
import { addTag, editPool, getHostMissingPatches, removeTag } from 'xo'
import { connectStore, formatSizeShort } from 'utils'
import { flatten, map, size, uniq } from 'lodash'
import {
createGetObjectsOfType,
createGetHostMetrics,
@@ -32,7 +30,7 @@ import styles from './index.css'
const getMissingPatches = createSelector(getPoolHosts, hosts => {
return Promise.all(map(hosts, host => getHostMissingPatches(host))).then(
patches => flatMap(patches)
patches => uniq(map(flatten(patches), 'name'))
)
})

View File

@@ -13,6 +13,7 @@ import { editHost, fetchHostStats, subscribeHostMissingPatches } from 'xo'
import { connectStore, routes } from 'utils'
import {
createDoesHostNeedRestart,
createFilter,
createGetObject,
createGetObjectsOfType,
createSelector,
@@ -76,6 +77,13 @@ const isRunning = host => host && host.power_state === 'Running'
createSelector(getPifs, pifs => map(pifs, pif => pif.$network))
)
const getPrivateNetworks = createFilter(
createGetObjectsOfType('network'),
createSelector(getPool, pool => network =>
network.$pool === pool.id && isEmpty(network.PIFs)
)
)
const getHostPatches = createSelector(
createGetObjectsOfType('pool_patch'),
createGetObjectsOfType('host_patch').pick(
@@ -114,6 +122,7 @@ const isRunning = host => host && host.power_state === 'Running'
nVms: getNumberOfVms(state, props),
pifs: getPifs(state, props),
pool: getPool(state, props),
privateNetworks: getPrivateNetworks(state, props),
vmController: getVmController(state, props),
vms: getHostVms(state, props),
}
@@ -317,6 +326,7 @@ export default class Host extends Component {
'nVms',
'pbds',
'pifs',
'privateNetworks',
'srs',
'vmController',
'vms',

View File

@@ -1,14 +1,12 @@
import _ from 'intl'
import ActionButton from 'action-button'
import Component from 'base-component'
import copy from 'copy-to-clipboard'
import React from 'react'
import Icon from 'icon'
import pick from 'lodash/pick'
import SingleLineRow from 'single-line-row'
import some from 'lodash/some'
import SortedTable from 'sorted-table'
import StateButton from 'state-button'
import TabButton from 'tab-button'
import Tooltip from 'tooltip'
import { confirm } from 'modal'
import { connectStore, noop } from 'utils'
@@ -18,9 +16,9 @@ import { error } from 'notification'
import { get } from '@xen-orchestra/defined'
import { Select, Number } from 'editable'
import { Toggle } from 'form'
import { isEmpty, pick, some } from 'lodash'
import {
connectPif,
createNetwork,
deletePif,
deletePifs,
disconnectPif,
@@ -211,7 +209,7 @@ class PifItemLock extends Component {
}
}
const COLUMNS = [
const PIF_COLUMNS = [
{
default: true,
itemRenderer: pif => pif.device,
@@ -293,7 +291,7 @@ const COLUMNS = [
},
]
const INDIVIDUAL_ACTIONS = [
const PIF_INDIVIDUAL_ACTIONS = [
{
handler: pif => copy(pif.uuid),
icon: 'clipboard',
@@ -307,7 +305,7 @@ const INDIVIDUAL_ACTIONS = [
},
]
const GROUPED_ACTIONS = [
const PIF_GROUPED_ACTIONS = [
{
handler: deletePifs,
icon: 'delete',
@@ -316,34 +314,79 @@ const GROUPED_ACTIONS = [
},
]
export default class TabNetwork extends Component {
render () {
return (
<Container>
<Row>
<Col className='text-xs-right'>
<TabButton
btnStyle='primary'
handler={createNetwork}
handlerParam={this.props.host}
icon='add'
labelId='networkCreateButton'
/>
</Col>
</Row>
<Row>
<Col>
<SortedTable
collection={this.props.pifs}
columns={COLUMNS}
groupedActions={GROUPED_ACTIONS}
individualActions={INDIVIDUAL_ACTIONS}
stateUrlParam='s'
userData={{ networks: this.props.networks }}
/>
</Col>
</Row>
</Container>
)
}
}
const PVT_NETWORK_COLUMNS = [
{
name: _('poolNetworkNameLabel'),
itemRenderer: network => network.name_label,
},
{
name: _('poolNetworkDescription'),
itemRenderer: network => network.name_description,
},
{
name: _('poolNetworkMTU'),
itemRenderer: network => network.MTU,
},
{
name: (
<div className='text-xs-center'>
<Tooltip content={_('defaultLockingMode')}>
<Icon size='lg' icon='lock' />
</Tooltip>
</div>
),
itemRenderer: network => (
<Icon icon={network.defaultIsLocked ? 'lock' : 'unlock'} />
),
},
]
const PVT_NETWORK_ACTIONS = [
{
handler: network => copy(network.uuid),
icon: 'clipboard',
label: network => _('copyUuid', { uuid: network.uuid }),
},
]
export default ({ host, networks, pifs, privateNetworks }) => (
<Container>
<Row>
<Col>
<h1>{_('poolNetworkPif')}</h1>
<SortedTable
collection={pifs}
columns={PIF_COLUMNS}
data-networks={networks}
groupedActions={PIF_GROUPED_ACTIONS}
individualActions={PIF_INDIVIDUAL_ACTIONS}
stateUrlParam='s'
/>
</Col>
</Row>
{!isEmpty(privateNetworks) && (
<Row>
<Col>
<h1>
{_('privateNetworks')}
<ActionButton
className='ml-1'
handler={noop}
icon='edit'
redirectOnSuccess={`/pools/${
host.$pool
}/network?s=${encodeURIComponent('!PIFs:length?')}`}
>
{_('manage')}
</ActionButton>
</h1>
<SortedTable
collection={privateNetworks}
columns={PVT_NETWORK_COLUMNS}
individualActions={PVT_NETWORK_ACTIONS}
/>
</Col>
</Row>
)}
</Container>
)

View File

@@ -376,7 +376,11 @@ export default class TabNetworks extends Component {
<Row>
<Col>
{!isEmpty(networks) ? (
<SortedTable collection={networks} columns={NETWORKS_COLUMNS} />
<SortedTable
collection={networks}
columns={NETWORKS_COLUMNS}
stateUrlParam='s'
/>
) : (
<h4 className='text-xs-center'>{_('poolNoNetwork')}</h4>
)}

View File

@@ -153,11 +153,7 @@ export default class Vm extends BaseComponent {
() => this.props.checkPermissions,
() => this.props.vm,
() => this.props.srs,
(checkPermissions, vm, srs) =>
checkPermissions([
[vm.id, 'administrate'],
...map(srs, sr => [sr.id, 'operate']),
])
(checkPermissions, vm, srs) => checkPermissions(vm.id, 'administrate')
)
_setNameDescription = nameDescription =>

3370
yarn.lock

File diff suppressed because it is too large Load Diff