Compare commits
16 Commits
token-last
...
improveFor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d70da2a960 | ||
|
|
637eb1d2d7 | ||
|
|
86b86c5c99 | ||
|
|
0b8525febe | ||
|
|
a3ea70c61c | ||
|
|
ae0f3b4fe0 | ||
|
|
2552ef37d2 | ||
|
|
9803e8c6cb | ||
|
|
3410cbc3b9 | ||
|
|
93fce0d4bf | ||
|
|
dbdc5f3e3b | ||
|
|
581b42fa9d | ||
|
|
e07e2d3ccd | ||
|
|
ad928ec23d | ||
|
|
1d7559ded2 | ||
|
|
9099b58557 |
@@ -7,8 +7,8 @@
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"dependencies": {
|
||||
"@xen-orchestra/async-map": "^0.1.2",
|
||||
"@xen-orchestra/backups": "^0.43.0",
|
||||
"@xen-orchestra/fs": "^4.1.0",
|
||||
"@xen-orchestra/backups": "^0.43.2",
|
||||
"@xen-orchestra/fs": "^4.1.1",
|
||||
"filenamify": "^6.0.0",
|
||||
"getopts": "^2.2.5",
|
||||
"lodash": "^4.17.15",
|
||||
|
||||
@@ -39,10 +39,12 @@ export const FullXapi = class FullXapiVmBackupRunner extends AbstractXapi {
|
||||
const vdis = await exportedVm.$getDisks()
|
||||
let maxStreamLength = 1024 * 1024 // Ovf file and tar headers are a few KB, let's stay safe
|
||||
for (const vdiRef of vdis) {
|
||||
const vdi = await this._xapi.getRecord(vdiRef)
|
||||
// at most the xva will take the physical usage of the disk
|
||||
// the resulting stream can be smaller due to the smaller block size for xva than vhd, and compression of xcp-ng
|
||||
maxStreamLength += vdi.physical_utilisation
|
||||
const vdi = await this._xapi.getRecord('VDI', vdiRef)
|
||||
|
||||
// the size a of fully allocated vdi will be virtual_size exaclty, it's a gross over evaluation
|
||||
// of the real stream size in general, since a disk is never completly full
|
||||
// vdi.physical_size seems to underevaluate a lot the real disk usage of a VDI, as of 2023-10-30
|
||||
maxStreamLength += vdi.virtual_size
|
||||
}
|
||||
|
||||
const sizeContainer = watchStreamSize(stream)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"version": "0.43.0",
|
||||
"version": "0.43.2",
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
},
|
||||
@@ -28,7 +28,7 @@
|
||||
"@vates/nbd-client": "^2.0.0",
|
||||
"@vates/parse-duration": "^0.1.1",
|
||||
"@xen-orchestra/async-map": "^0.1.2",
|
||||
"@xen-orchestra/fs": "^4.1.0",
|
||||
"@xen-orchestra/fs": "^4.1.1",
|
||||
"@xen-orchestra/log": "^0.6.0",
|
||||
"@xen-orchestra/template": "^0.1.0",
|
||||
"app-conf": "^2.3.0",
|
||||
@@ -56,7 +56,7 @@
|
||||
"tmp": "^0.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xen-orchestra/xapi": "^3.2.0"
|
||||
"@xen-orchestra/xapi": "^3.3.0"
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/fs",
|
||||
"version": "4.1.0",
|
||||
"version": "4.1.1",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"description": "The File System for Xen Orchestra backups.",
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/fs",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "https://vates.fr"
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.0",
|
||||
"engines": {
|
||||
"node": ">=15.6"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "@xen-orchestra/proxy",
|
||||
"version": "0.26.35",
|
||||
"version": "0.26.37",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"description": "XO Proxy used to remotely execute backup jobs",
|
||||
"keywords": [
|
||||
@@ -32,13 +32,13 @@
|
||||
"@vates/decorate-with": "^2.0.0",
|
||||
"@vates/disposable": "^0.1.4",
|
||||
"@xen-orchestra/async-map": "^0.1.2",
|
||||
"@xen-orchestra/backups": "^0.43.0",
|
||||
"@xen-orchestra/fs": "^4.1.0",
|
||||
"@xen-orchestra/backups": "^0.43.2",
|
||||
"@xen-orchestra/fs": "^4.1.1",
|
||||
"@xen-orchestra/log": "^0.6.0",
|
||||
"@xen-orchestra/mixin": "^0.1.0",
|
||||
"@xen-orchestra/mixins": "^0.13.0",
|
||||
"@xen-orchestra/mixins": "^0.14.0",
|
||||
"@xen-orchestra/self-signed": "^0.1.3",
|
||||
"@xen-orchestra/xapi": "^3.2.0",
|
||||
"@xen-orchestra/xapi": "^3.3.0",
|
||||
"ajv": "^8.0.3",
|
||||
"app-conf": "^2.3.0",
|
||||
"async-iterator-to-stream": "^1.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xen-orchestra/xapi",
|
||||
"version": "3.2.0",
|
||||
"version": "3.3.0",
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/xapi",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
|
||||
58
CHANGELOG.md
58
CHANGELOG.md
@@ -1,8 +1,62 @@
|
||||
# ChangeLog
|
||||
|
||||
## **5.88.0** (2023-10-31)
|
||||
|
||||
<img id="latest" src="https://badgen.net/badge/channel/latest/yellow" alt="Channel: latest" />
|
||||
|
||||
### Highlights
|
||||
|
||||
- [About] For source users, display if their XO is up to date [#5934](https://github.com/vatesfr/xen-orchestra/issues/5934) (PR [#7091](https://github.com/vatesfr/xen-orchestra/pull/7091))
|
||||
- [Self] Show number of VMs that belong to each Resource Set (PR [#7114](https://github.com/vatesfr/xen-orchestra/pull/7114))
|
||||
- [VM/New] Possibility to create and attach a _VTPM_ to a VM [#7066](https://github.com/vatesfr/xen-orchestra/issues/7066) [Forum#6578](https://xcp-ng.org/forum/topic/6578/xcp-ng-8-3-public-alpha/109) (PR [#7077](https://github.com/vatesfr/xen-orchestra/pull/7077))
|
||||
- [XOSTOR] Ability to create a XOSTOR storage (PR [#6983](https://github.com/vatesfr/xen-orchestra/pull/6983))
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [Host/Advanced] Allow to force _Smart reboot_ if some resident VMs have the suspend operation blocked [Forum#7136](https://xcp-ng.org/forum/topic/7136/suspending-vms-during-host-reboot/23) (PR [#7025](https://github.com/vatesfr/xen-orchestra/pull/7025))
|
||||
- [Plugin/backup-report] Errors are now listed in XO tasks
|
||||
- [PIF] Show network name in PIF selectors (PR [#7081](https://github.com/vatesfr/xen-orchestra/pull/7081))
|
||||
- [VM/Advanced] Possibility to create/delete VTPM [#7066](https://github.com/vatesfr/xen-orchestra/issues/7066) [Forum#6578](https://xcp-ng.org/forum/topic/6578/xcp-ng-8-3-public-alpha/109) (PR [#7085](https://github.com/vatesfr/xen-orchestra/pull/7085))
|
||||
- [Dashboard/Health] Displays number of VDIs to coalesce (PR [#7111](https://github.com/vatesfr/xen-orchestra/pull/7111))
|
||||
- [Proxy] Ability to open support tunnel on XO Proxy (PRs [#7126](https://github.com/vatesfr/xen-orchestra/pull/7126) [#7127](https://github.com/vatesfr/xen-orchestra/pull/7127))
|
||||
- [New network] Remove bonded PIFs from selector when creating network (PR [#7136](https://github.com/vatesfr/xen-orchestra/pull/7136))
|
||||
- Try to preserve current page across reauthentication (PR [#7013](https://github.com/vatesfr/xen-orchestra/pull/7013))
|
||||
- [XO-WEB/Forget SR] Changed the modal message and added a confirmation text to be sure the action is understood by the user (PR [#7154](https://github.com/vatesfr/xen-orchestra/pull/7154))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [Rolling Pool Update] After the update, when migrating VMs back to their host, do not migrate VMs that are already on the right host [Forum#7802](https://xcp-ng.org/forum/topic/7802) (PR [#7071](https://github.com/vatesfr/xen-orchestra/pull/7071))
|
||||
- [RPU] Fix "XenServer credentials not found" when running a Rolling Pool Update on a XenServer pool (PR [#7089](https://github.com/vatesfr/xen-orchestra/pull/7089))
|
||||
- [Usage report] Fix "Converting circular structure to JSON" error
|
||||
- [Home] Fix OS icons alignment (PR [#7090](https://github.com/vatesfr/xen-orchestra/pull/7090))
|
||||
- [SR/Advanced] Fix the total number of VDIs to coalesce by taking into account common chains [#7016](https://github.com/vatesfr/xen-orchestra/issues/7016) (PR [#7098](https://github.com/vatesfr/xen-orchestra/pull/7098))
|
||||
- Don't require to sign in again in XO after losing connection to XO Server (e.g. when restarting or upgrading XO) (PR [#7103](https://github.com/vatesfr/xen-orchestra/pull/7103))
|
||||
- [Usage report] Fix "Converting circular structure to JSON" error (PR [#7096](https://github.com/vatesfr/xen-orchestra/pull/7096))
|
||||
- [Usage report] Fix "Cannot convert undefined or null to object" error (PR [#7092](https://github.com/vatesfr/xen-orchestra/pull/7092))
|
||||
- [Plugin/transport-xmpp] Fix plugin load
|
||||
- [Self Service] Fix Self users not being able to snapshot VMs when they're members of a user group (PR [#7129](https://github.com/vatesfr/xen-orchestra/pull/7129))
|
||||
- [Netbox] Fix "The selected cluster is not assigned to this site" error [Forum#7887](https://xcp-ng.org/forum/topic/7887) (PR [#7124](https://github.com/vatesfr/xen-orchestra/pull/7124))
|
||||
- [Backups] Fix `MESSAGE_METHOD_UNKNOWN` during full backup [Forum#7894](https://xcp-ng.org/forum/topic/7894)(PR [#7139](https://github.com/vatesfr/xen-orchestra/pull/7139))
|
||||
- [Resource Set] Fix error displayed after successful VM addition to resource set PR ([#7144](https://github.com/vatesfr/xen-orchestra/pull/7144))
|
||||
|
||||
### Released packages
|
||||
|
||||
- @xen-orchestra/fs 4.1.1
|
||||
- @xen-orchestra/xapi 3.3.0
|
||||
- @xen-orchestra/mixins 0.14.0
|
||||
- xo-server-backup-reports 0.18.0
|
||||
- xo-server-transport-xmpp 0.1.3
|
||||
- xo-server-usage-report 0.10.5
|
||||
- @xen-orchestra/backups 0.43.2
|
||||
- @xen-orchestra/proxy 0.26.37
|
||||
- xo-cli 0.21.0
|
||||
- xo-server 5.125.1
|
||||
- xo-server-netbox 1.3.2
|
||||
- xo-web 5.127.1
|
||||
|
||||
## **5.87.0** (2023-09-29)
|
||||
|
||||
<img id="latest" src="https://badgen.net/badge/channel/latest/yellow" alt="Channel: latest" />
|
||||
<img id="stable" src="https://badgen.net/badge/channel/stable/green" alt="Channel: stable" />
|
||||
|
||||
### Highlights
|
||||
|
||||
@@ -53,8 +107,6 @@
|
||||
|
||||
## **5.86.1** (2023-09-07)
|
||||
|
||||
<img id="stable" src="https://badgen.net/badge/channel/stable/green" alt="Channel: stable" />
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [User] _Forget all connection tokens_ button should not delete other users' tokens, even when current user is an administrator (PR [#7014](https://github.com/vatesfr/xen-orchestra/pull/7014))
|
||||
|
||||
@@ -7,31 +7,11 @@
|
||||
|
||||
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
||||
|
||||
- [Host/Advanced] Allow to force _Smart reboot_ if some resident VMs have the suspend operation blocked [Forum#7136](https://xcp-ng.org/forum/topic/7136/suspending-vms-during-host-reboot/23) (PR [#7025](https://github.com/vatesfr/xen-orchestra/pull/7025))
|
||||
- [Plugin/backup-report] Errors are now listed in XO tasks
|
||||
- [PIF] Show network name in PIF selectors (PR [#7081](https://github.com/vatesfr/xen-orchestra/pull/7081))
|
||||
- [VM/Advanced] Possibility to create/delete VTPM [#7066](https://github.com/vatesfr/xen-orchestra/issues/7066) [Forum#6578](https://xcp-ng.org/forum/topic/6578/xcp-ng-8-3-public-alpha/109) (PR [#7085](https://github.com/vatesfr/xen-orchestra/pull/7085))
|
||||
- [VM/New] Possibility to create and attach a _VTPM_ to a VM [#7066](https://github.com/vatesfr/xen-orchestra/issues/7066) [Forum#6578](https://xcp-ng.org/forum/topic/6578/xcp-ng-8-3-public-alpha/109) (PR [#7077](https://github.com/vatesfr/xen-orchestra/pull/7077))
|
||||
- [Dashboard/Health] Displays number of VDIs to coalesce (PR [#7111](https://github.com/vatesfr/xen-orchestra/pull/7111))
|
||||
- [Self] Show number of VMs that belong to each Resource Set (PR [#7114](https://github.com/vatesfr/xen-orchestra/pull/7114))
|
||||
- [About] For source users, display if their XO is up to date [#5934](https://github.com/vatesfr/xen-orchestra/issues/5934) (PR [#7091](https://github.com/vatesfr/xen-orchestra/pull/7091))
|
||||
- [Proxy] Ability to open support tunnel on XO Proxy (PRs [#7126](https://github.com/vatesfr/xen-orchestra/pull/7126) [#7127](https://github.com/vatesfr/xen-orchestra/pull/7127))
|
||||
- [XOSTOR] Ability to create a XOSTOR storage (PR [#6983](https://github.com/vatesfr/xen-orchestra/pull/6983))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
||||
|
||||
- [Rolling Pool Update] After the update, when migrating VMs back to their host, do not migrate VMs that are already on the right host [Forum#7802](https://xcp-ng.org/forum/topic/7802) (PR [#7071](https://github.com/vatesfr/xen-orchestra/pull/7071))
|
||||
- [RPU] Fix "XenServer credentials not found" when running a Rolling Pool Update on a XenServer pool (PR [#7089](https://github.com/vatesfr/xen-orchestra/pull/7089))
|
||||
- [Usage report] Fix "Converting circular structure to JSON" error
|
||||
- [Home] Fix OS icons alignment (PR [#7090](https://github.com/vatesfr/xen-orchestra/pull/7090))
|
||||
- [SR/Advanced] Fix the total number of VDIs to coalesce by taking into account common chains [#7016](https://github.com/vatesfr/xen-orchestra/issues/7016) (PR [#7098](https://github.com/vatesfr/xen-orchestra/pull/7098))
|
||||
- Don't require to sign in again in XO after losing connection to XO Server (e.g. when restarting or upgrading XO) (PR [#7103](https://github.com/vatesfr/xen-orchestra/pull/7103))
|
||||
- [Usage report] Fix "Converting circular structure to JSON" error (PR [#7096](https://github.com/vatesfr/xen-orchestra/pull/7096))
|
||||
- [Usage report] Fix "Cannot convert undefined or null to object" error (PR [#7092](https://github.com/vatesfr/xen-orchestra/pull/7092))
|
||||
- [Plugin/transport-xmpp] Fix plugin load
|
||||
- [Self Service] Fix Self users not being able to snapshot VMs when they're members of a user group (PR [#7129](https://github.com/vatesfr/xen-orchestra/pull/7129))
|
||||
- [Netbox] Fix VMs' `site` property being unnecessarily updated on some versions of Netbox (PR [#7145](https://github.com/vatesfr/xen-orchestra/pull/7145))
|
||||
|
||||
### Packages to release
|
||||
|
||||
@@ -49,15 +29,6 @@
|
||||
|
||||
<!--packages-start-->
|
||||
|
||||
- @xen-orchestra/backups patch
|
||||
- @xen-orchestra/fs patch
|
||||
- @xen-orchestra/mixins minor
|
||||
- @xen-orchestra/xapi minor
|
||||
- xo-server minor
|
||||
- xo-server-backup-reports minor
|
||||
- xo-server-netbox patch
|
||||
- xo-server-transport-xmpp patch
|
||||
- xo-server-usage-report patch
|
||||
- xo-web minor
|
||||
|
||||
<!--packages-end-->
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"node": ">=10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xen-orchestra/fs": "^4.1.0",
|
||||
"@xen-orchestra/fs": "^4.1.1",
|
||||
"cli-progress": "^3.1.0",
|
||||
"exec-promise": "^0.7.0",
|
||||
"getopts": "^2.2.3",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"@vates/read-chunk": "^1.2.0",
|
||||
"@vates/stream-reader": "^0.1.0",
|
||||
"@xen-orchestra/async-map": "^0.1.2",
|
||||
"@xen-orchestra/fs": "^4.1.0",
|
||||
"@xen-orchestra/fs": "^4.1.1",
|
||||
"@xen-orchestra/log": "^0.6.0",
|
||||
"async-iterator-to-stream": "^1.0.2",
|
||||
"decorator-synchronized": "^0.6.0",
|
||||
@@ -33,7 +33,7 @@
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@xen-orchestra/fs": "^4.1.0",
|
||||
"@xen-orchestra/fs": "^4.1.1",
|
||||
"execa": "^5.0.0",
|
||||
"get-stream": "^6.0.0",
|
||||
"rimraf": "^5.0.1",
|
||||
|
||||
@@ -13,6 +13,7 @@ import humanFormat from 'human-format'
|
||||
import identity from 'lodash/identity.js'
|
||||
import isObject from 'lodash/isObject.js'
|
||||
import micromatch from 'micromatch'
|
||||
import os from 'os'
|
||||
import pairs from 'lodash/toPairs.js'
|
||||
import pick from 'lodash/pick.js'
|
||||
import prettyMs from 'pretty-ms'
|
||||
@@ -47,7 +48,7 @@ async function connect() {
|
||||
return xo
|
||||
}
|
||||
|
||||
async function parseRegisterArgs(args, tokenDescription, acceptToken = false) {
|
||||
async function parseRegisterArgs(args, tokenDescription, client, acceptToken = false) {
|
||||
const {
|
||||
allowUnauthorized,
|
||||
expiresIn,
|
||||
@@ -84,21 +85,21 @@ async function parseRegisterArgs(args, tokenDescription, acceptToken = false) {
|
||||
pw(resolve)
|
||||
}),
|
||||
] = opts
|
||||
result.token = await _createToken({ ...result, description: tokenDescription, email, password })
|
||||
result.token = await _createToken({ ...result, client, description: tokenDescription, email, password })
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async function _createToken({ allowUnauthorized, description, email, expiresIn, password, url }) {
|
||||
async function _createToken({ allowUnauthorized, client, description, email, expiresIn, password, url }) {
|
||||
const xo = new Xo({ rejectUnauthorized: !allowUnauthorized, url })
|
||||
await xo.open()
|
||||
try {
|
||||
await xo.signIn({ email, password })
|
||||
console.warn('Successfully logged with', xo.user.email)
|
||||
|
||||
return await xo.call('token.create', { description, expiresIn }).catch(error => {
|
||||
// if invalid parameter error, retry without description for backward compatibility
|
||||
return await xo.call('token.create', { client, description, expiresIn }).catch(error => {
|
||||
// if invalid parameter error, retry without client and description for backward compatibility
|
||||
if (error.code === 10) {
|
||||
return xo.call('token.create', { expiresIn })
|
||||
}
|
||||
@@ -219,6 +220,8 @@ function wrap(val) {
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const PACKAGE_JSON = JSON.parse(readFileSync(new URL('package.json', import.meta.url)))
|
||||
|
||||
const help = wrap(
|
||||
(function (pkg) {
|
||||
return `Usage:
|
||||
@@ -355,7 +358,7 @@ $name v$version`.replace(/<([^>]+)>|\$(\w+)/g, function (_, arg, key) {
|
||||
|
||||
return pkg[key]
|
||||
})
|
||||
})(JSON.parse(readFileSync(new URL('package.json', import.meta.url))))
|
||||
})(PACKAGE_JSON)
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
@@ -422,9 +425,18 @@ async function createToken(args) {
|
||||
COMMANDS.createToken = createToken
|
||||
|
||||
async function register(args) {
|
||||
const opts = await parseRegisterArgs(args, 'xo-cli --register', true)
|
||||
let { clientId } = await config.load()
|
||||
if (clientId === undefined) {
|
||||
clientId = Math.random().toString(36).slice(2)
|
||||
}
|
||||
|
||||
const { name, version } = PACKAGE_JSON
|
||||
const label = `${name}@${version} - ${os.hostname()} - ${os.type()} ${os.machine()}`
|
||||
|
||||
const opts = await parseRegisterArgs(args, label, { id: clientId }, true)
|
||||
await config.set({
|
||||
allowUnauthorized: opts.allowUnauthorized,
|
||||
clientId,
|
||||
server: opts.url,
|
||||
token: opts.token,
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xo-cli",
|
||||
"version": "0.20.0",
|
||||
"version": "0.21.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"description": "Basic CLI for Xen-Orchestra",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xo-server-backup-reports",
|
||||
"version": "0.17.4",
|
||||
"version": "0.18.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"description": "Backup reports plugin for XO-Server",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xo-server-netbox",
|
||||
"version": "1.3.0",
|
||||
"version": "1.3.2",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"description": "Synchronizes pools managed by Xen Orchestra with Netbox",
|
||||
"keywords": [
|
||||
|
||||
@@ -144,7 +144,9 @@ class Netbox {
|
||||
const httpRequest = async () => {
|
||||
try {
|
||||
const response = await this.#xo.httpRequest(url, options)
|
||||
this.#netboxApiVersion = response.headers['api-version']
|
||||
// API version only follows minor version, which is less precise and is not semver-valid
|
||||
// See https://github.com/netbox-community/netbox/issues/12879#issuecomment-1589190236
|
||||
this.#netboxApiVersion = semver.coerce(response.headers['api-version'])?.version ?? undefined
|
||||
const body = await response.text()
|
||||
if (body.length > 0) {
|
||||
return JSON.parse(body)
|
||||
@@ -336,6 +338,14 @@ class Netbox {
|
||||
tags: [],
|
||||
}
|
||||
|
||||
// Prior to Netbox v3.3.0: no "site" field on VMs
|
||||
// v3.3.0: "site" is REQUIRED and MUST be the same as cluster's site
|
||||
// v3.3.5: "site" is OPTIONAL (auto-assigned in UI, not in API). `null` and cluster's site are accepted.
|
||||
// v3.4.8: "site" is OPTIONAL and AUTO-ASSIGNED with cluster's site. If passed: ignored except if site is different from cluster's, then error.
|
||||
if (this.#netboxApiVersion === undefined || semver.satisfies(this.#netboxApiVersion, '3.3.0 - 3.4.7')) {
|
||||
nbVm.site = find(nbClusters, { id: nbCluster.id })?.site?.id ?? null
|
||||
}
|
||||
|
||||
const distro = xoVm.os_version?.distro
|
||||
if (distro != null) {
|
||||
const slug = slugify(distro)
|
||||
@@ -379,10 +389,7 @@ class Netbox {
|
||||
nbVm.tags = nbVmTags.sort(({ id: id1 }, { id: id2 }) => (id1 < id2 ? -1 : 1))
|
||||
|
||||
// https://netbox.readthedocs.io/en/stable/release-notes/version-2.7/#api-choice-fields-now-use-string-values-3569
|
||||
if (
|
||||
this.#netboxApiVersion !== undefined &&
|
||||
!semver.satisfies(semver.coerce(this.#netboxApiVersion).version, '>=2.7.0')
|
||||
) {
|
||||
if (this.#netboxApiVersion !== undefined && !semver.satisfies(this.#netboxApiVersion, '>=2.7.0')) {
|
||||
nbVm.status = xoVm.power_state === 'Running' ? 1 : 0
|
||||
}
|
||||
|
||||
@@ -395,6 +402,9 @@ class Netbox {
|
||||
cluster: nbVm.cluster?.id ?? null,
|
||||
status: nbVm.status?.value ?? null,
|
||||
platform: nbVm.platform?.id ?? null,
|
||||
// If site is not supported by Netbox, its value is undefined
|
||||
// If site is supported by Netbox but empty, its value is null
|
||||
site: nbVm.site == null ? nbVm.site : nbVm.site.id,
|
||||
// Sort them so that they can be compared by diff()
|
||||
tags: nbVm.tags.map(nbTag => ({ id: nbTag.id })).sort(({ id: id1 }, { id: id2 }) => (id1 < id2 ? -1 : 1)),
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xo-server-transport-xmpp",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"description": "Transport Xmpp plugin for XO-Server",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xo-server-usage-report",
|
||||
"version": "0.10.4",
|
||||
"version": "0.10.5",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"description": "Report resources usage with their evolution",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "xo-server",
|
||||
"version": "5.124.0",
|
||||
"version": "5.125.1",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"description": "Server part of Xen-Orchestra",
|
||||
"keywords": [
|
||||
@@ -41,18 +41,18 @@
|
||||
"@vates/predicates": "^1.1.0",
|
||||
"@vates/read-chunk": "^1.2.0",
|
||||
"@xen-orchestra/async-map": "^0.1.2",
|
||||
"@xen-orchestra/backups": "^0.43.0",
|
||||
"@xen-orchestra/backups": "^0.43.2",
|
||||
"@xen-orchestra/cron": "^1.0.6",
|
||||
"@xen-orchestra/defined": "^0.0.1",
|
||||
"@xen-orchestra/emit-async": "^1.0.0",
|
||||
"@xen-orchestra/fs": "^4.1.0",
|
||||
"@xen-orchestra/fs": "^4.1.1",
|
||||
"@xen-orchestra/log": "^0.6.0",
|
||||
"@xen-orchestra/mixin": "^0.1.0",
|
||||
"@xen-orchestra/mixins": "^0.13.0",
|
||||
"@xen-orchestra/mixins": "^0.14.0",
|
||||
"@xen-orchestra/self-signed": "^0.1.3",
|
||||
"@xen-orchestra/template": "^0.1.0",
|
||||
"@xen-orchestra/vmware-explorer": "^0.3.0",
|
||||
"@xen-orchestra/xapi": "^3.2.0",
|
||||
"@xen-orchestra/xapi": "^3.3.0",
|
||||
"ajv": "^8.0.3",
|
||||
"app-conf": "^2.3.0",
|
||||
"async-iterator-to-stream": "^1.0.1",
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// TODO: Prevent token connections from creating tokens.
|
||||
// TODO: Token permission.
|
||||
export async function create({ description, expiresIn }) {
|
||||
export async function create({ client, description, expiresIn }) {
|
||||
return (
|
||||
await this.createAuthenticationToken({
|
||||
client,
|
||||
description,
|
||||
expiresIn,
|
||||
userId: this.apiContext.user.id,
|
||||
@@ -17,6 +18,15 @@ create.params = {
|
||||
optional: true,
|
||||
type: 'string',
|
||||
},
|
||||
client: {
|
||||
description:
|
||||
'client this authentication token belongs to, if a previous token exists, it will be updated and returned',
|
||||
optional: true,
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { description: 'unique identifier of this client', type: 'string' },
|
||||
},
|
||||
},
|
||||
expiresIn: {
|
||||
optional: true,
|
||||
type: ['number', 'string'],
|
||||
|
||||
@@ -3,7 +3,25 @@ import Collection from '../collection/redis.mjs'
|
||||
// ===================================================================
|
||||
|
||||
export class Tokens extends Collection {
|
||||
_serialize(token) {
|
||||
const { client } = token
|
||||
if (client !== undefined) {
|
||||
const { id, ...rest } = client
|
||||
token.client_id = id
|
||||
token.client = JSON.stringify(rest)
|
||||
}
|
||||
}
|
||||
|
||||
_unserialize(token) {
|
||||
const { client, client_id } = token
|
||||
if (client !== undefined) {
|
||||
token.client = {
|
||||
...JSON.parse(client),
|
||||
id: client_id,
|
||||
}
|
||||
delete token.client_id
|
||||
}
|
||||
|
||||
if (token.created_at !== undefined) {
|
||||
token.created_at = +token.created_at
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export default class {
|
||||
const tokensDb = (this._tokens = new Tokens({
|
||||
connection: app._redis,
|
||||
namespace: 'token',
|
||||
indexes: ['user_id'],
|
||||
indexes: ['client_id', 'user_id'],
|
||||
}))
|
||||
|
||||
app.addConfigManager(
|
||||
@@ -180,7 +180,7 @@ export default class {
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
async createAuthenticationToken({ description, expiresIn, userId }) {
|
||||
async createAuthenticationToken({ client, description, expiresIn, userId }) {
|
||||
let duration = this._defaultTokenValidity
|
||||
if (expiresIn !== undefined) {
|
||||
duration = parseDuration(expiresIn)
|
||||
@@ -191,8 +191,27 @@ export default class {
|
||||
}
|
||||
}
|
||||
|
||||
const tokens = this._tokens
|
||||
const now = Date.now()
|
||||
|
||||
const clientId = client?.id
|
||||
if (clientId !== undefined) {
|
||||
const token = await tokens.first({ client_id: clientId, user_id: userId })
|
||||
if (token !== undefined) {
|
||||
if (token.expiration > now) {
|
||||
token.description = description
|
||||
token.expiration = now + duration
|
||||
tokens.update(token)::ignoreErrors()
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
tokens.remove(token.id)::ignoreErrors()
|
||||
}
|
||||
}
|
||||
|
||||
const token = {
|
||||
client,
|
||||
created_at: now,
|
||||
description,
|
||||
id: await generateToken(),
|
||||
|
||||
@@ -444,6 +444,7 @@ export default class {
|
||||
|
||||
async shareVmResourceSet(vmId) {
|
||||
const xapi = this._app.getXapi(vmId)
|
||||
await xapi.barrier(xapi.getObject(vmId).$ref)
|
||||
const resourceSetId = xapi.xo.getData(vmId, 'resourceSet')
|
||||
if (resourceSetId === undefined) {
|
||||
throw new Error('the vm is not in a resource set')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "xo-web",
|
||||
"version": "5.126.0",
|
||||
"version": "5.127.1",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"description": "Web interface client for Xen-Orchestra",
|
||||
"keywords": [
|
||||
|
||||
@@ -1103,7 +1103,7 @@ const messages = {
|
||||
installAllPatchesContent: 'To install all patches go to pool.',
|
||||
installAllPatchesRedirect: 'Go to pool',
|
||||
installAllPatchesOnHostContent:
|
||||
'This will automatically restart the toolstack. Running VMs will not be affected. Are you sure you want to continue and install all patches on this host?',
|
||||
'The pool master must always be updated FIRST. Updating will automatically restart the toolstack. Running VMs will not be affected. Are you sure you want to continue and install all patches on this host?',
|
||||
patchRelease: 'Release',
|
||||
updatePluginNotInstalled:
|
||||
'An error occurred while fetching the patches. Please make sure the updater plugin is installed by running `yum install xcp-ng-updater` on the host.',
|
||||
@@ -2372,9 +2372,9 @@ const messages = {
|
||||
'This will disconnect each selected SR from its host (local SR) or from every hosts of its pool (shared SR).',
|
||||
srForgetModalTitle: 'Forget SR',
|
||||
srsForgetModalTitle: 'Forget selected SRs',
|
||||
srForgetModalMessage: "Are you sure you want to forget this SR? VDIs on this storage won't be removed.",
|
||||
srForgetModalMessage: "Are you sure you want to forget this SR? You will lose all the metadata for it, meaning all the links between the VDIs (disks) and their respective VMs. This operation cannot be undone.",
|
||||
srsForgetModalMessage:
|
||||
"Are you sure you want to forget all the selected SRs? VDIs on these storages won't be removed.",
|
||||
"Are you sure you want to forget {nPbds, number} SR{nPbds, plural, one {} other {s}}? You will lose all the metadata for it, meaning all the links between the VDIs (disks) and their respective VMs. This operation cannot be undone.",
|
||||
srAllDisconnected: 'Disconnected',
|
||||
srSomeConnected: 'Partially connected',
|
||||
srAllConnected: 'Connected',
|
||||
@@ -2504,6 +2504,10 @@ const messages = {
|
||||
fieldsMissing: 'Some fields are missing',
|
||||
hostsNotSameNumberOfDisks: 'Hosts do not have the same number of disks',
|
||||
isTapdevsDisk: 'This is "tapdevs" disk',
|
||||
licenseBoundUnknownXostor: 'License attached to an unknown XOSTOR',
|
||||
licenseNotBoundXostor: 'No XOSTOR attached',
|
||||
licenseExpiredXostorWarning:
|
||||
'The license {licenseId} has expired. You can still use the SR but cannot administrate it anymore.',
|
||||
networks: 'Networks',
|
||||
notXcpPool: 'Not an XCP-ng pool',
|
||||
noXostorFound: 'No XOSTOR found',
|
||||
|
||||
@@ -2268,15 +2268,31 @@ export const deleteSr = sr =>
|
||||
|
||||
export const fetchSrStats = (sr, granularity) => _call('sr.stats', { id: resolveId(sr), granularity })
|
||||
|
||||
export const forgetSr = sr =>
|
||||
export const forgetSr = sr => {
|
||||
confirm({
|
||||
title: _('srForgetModalTitle'),
|
||||
body: _('srForgetModalMessage'),
|
||||
}).then(() => _call('sr.forget', { id: resolveId(sr) }), noop)
|
||||
body: (
|
||||
<div className='text-warning'>
|
||||
<p className='font-weight-bold'>{_('srForgetModalMessage')}</p>
|
||||
</div>
|
||||
),
|
||||
strongConfirm: {
|
||||
messageId: 'srForget',
|
||||
},
|
||||
}).then(() => _call('sr.forget', { id: resolveId(sr) }), noop);
|
||||
};
|
||||
|
||||
export const forgetSrs = srs =>
|
||||
confirm({
|
||||
title: _('srsForgetModalTitle'),
|
||||
body: _('srsForgetModalMessage'),
|
||||
body: (
|
||||
<div className='text-warning'>
|
||||
<p className='font-weight-bold'>{_('srsForgetModalMessage')}</p>
|
||||
</div>
|
||||
),
|
||||
strongConfirm: {
|
||||
messageId: 'srsForget',
|
||||
},
|
||||
}).then(() => Promise.all(map(resolveIds(srs), id => _call('sr.forget', { id }))), noop)
|
||||
|
||||
export const reconnectAllHostsSr = sr =>
|
||||
|
||||
@@ -156,7 +156,7 @@ const NewNetwork = decorate([
|
||||
pifPredicate:
|
||||
(_, { pool }) =>
|
||||
pif =>
|
||||
!pif.isBondSlave && pif.vlan === -1 && pif.$host === (pool && pool.master),
|
||||
!pif.isBondSlave && !pif.isBondMaster && pif.vlan === -1 && pif.$host === (pool && pool.master),
|
||||
pifPredicateSdnController:
|
||||
(_, { pool }) =>
|
||||
pif =>
|
||||
|
||||
@@ -128,6 +128,22 @@ const LicenseManager = ({ item, userData }) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'xostor') {
|
||||
const { srId } = item
|
||||
|
||||
if (srId === undefined) {
|
||||
return _('licenseNotBoundXostor')
|
||||
}
|
||||
|
||||
const sr = userData.xostorSrs[srId]
|
||||
return (
|
||||
<span>
|
||||
{sr === undefined ? _('licenseBoundUnknownXostor') : <Link to={`srs/${sr.id}`}>{renderXoItem(sr)}</Link>}{' '}
|
||||
<CopyToClipboardButton value={srId} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
console.warn('encountered unsupported license type')
|
||||
return null
|
||||
}
|
||||
@@ -174,11 +190,15 @@ const PRODUCTS_COLUMNS = [
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@adminOnly
|
||||
@connectStore({
|
||||
xosanSrs: createGetObjectsOfType('SR').filter([
|
||||
({ SR_type }) => SR_type === 'xosan', // eslint-disable-line camelcase
|
||||
]),
|
||||
xoaRegistration: state => state.xoaRegisterState,
|
||||
@connectStore(() => {
|
||||
const getSrs = createGetObjectsOfType('SR')
|
||||
return {
|
||||
xosanSrs: getSrs.filter([
|
||||
({ SR_type }) => SR_type === 'xosan', // eslint-disable-line camelcase
|
||||
]),
|
||||
xoaRegistration: state => state.xoaRegisterState,
|
||||
xostorSrs: getSrs.filter([({ SR_type }) => SR_type === 'linstor']),
|
||||
}
|
||||
})
|
||||
@addSubscriptions(() => ({
|
||||
plugins: subscribePlugins,
|
||||
@@ -363,7 +383,7 @@ export default class Licenses extends Component {
|
||||
return <em>{_('statusLoading')}</em>
|
||||
}
|
||||
|
||||
const { xoaRegistration, selfLicenses, xosanSrs } = this.props
|
||||
const { xoaRegistration, selfLicenses, xosanSrs, xostorSrs } = this.props
|
||||
|
||||
return (
|
||||
<Container>
|
||||
@@ -390,6 +410,7 @@ export default class Licenses extends Component {
|
||||
data-registeredEmail={xoaRegistration.email}
|
||||
data-selfLicenses={selfLicenses}
|
||||
data-xosanSrs={xosanSrs}
|
||||
data-xostorSrs={xostorSrs}
|
||||
stateUrlParam='s'
|
||||
/>
|
||||
</Col>
|
||||
|
||||
@@ -6,14 +6,15 @@ import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import SelectLicense from 'select-license'
|
||||
import SortedTable from 'sorted-table'
|
||||
import Tooltip from 'tooltip'
|
||||
import { bindLicense } from 'xo'
|
||||
import { connectStore } from 'utils'
|
||||
import { createGetObjectsOfType } from 'selectors'
|
||||
import { createGetObjectsOfType, createSelector } from 'selectors'
|
||||
import { groupBy } from 'lodash'
|
||||
import { injectState, provideState } from 'reaclette'
|
||||
import { Pool, Sr } from 'render-xo-item'
|
||||
|
||||
import BulkIcons from '../../../common/bulk-icons'
|
||||
|
||||
class XostorLicensesForm extends Component {
|
||||
state = {
|
||||
licenseId: 'none',
|
||||
@@ -24,40 +25,72 @@ class XostorLicensesForm extends Component {
|
||||
return bindLicense(this.state.licenseId, item.uuid).then(userData.updateLicenses)
|
||||
}
|
||||
|
||||
getAlerts = createSelector(
|
||||
() => this.props.item,
|
||||
() => this.props.userData,
|
||||
(sr, userData) => {
|
||||
const alerts = []
|
||||
const licenses = userData.licensesByXostorUuid[sr.id]
|
||||
|
||||
// Xostor bound to multiple licenses
|
||||
if (licenses?.length > 1) {
|
||||
alerts.push({
|
||||
level: 'danger',
|
||||
render: (
|
||||
<p>
|
||||
{_('xostorMultipleLicenses')}
|
||||
<br />
|
||||
{licenses.map(license => license.id.slice(-4)).join(',')}
|
||||
</p>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
const license = licenses?.[0]
|
||||
if (license?.expires < Date.now()) {
|
||||
alerts.push({
|
||||
level: 'danger',
|
||||
render: _('licenseExpiredXostorWarning', { licenseId: license?.id.slice(-4) }),
|
||||
})
|
||||
}
|
||||
return alerts
|
||||
}
|
||||
)
|
||||
|
||||
render() {
|
||||
const alerts = this.getAlerts()
|
||||
if (alerts.length > 0) {
|
||||
return <BulkIcons alerts={alerts} />
|
||||
}
|
||||
|
||||
const { item, userData } = this.props
|
||||
const { licenseId } = this.state
|
||||
const licenses = userData.licensesByXostorUuid[item.id]
|
||||
|
||||
// Xostor bound to multiple licenses
|
||||
if (licenses?.length > 1) {
|
||||
return (
|
||||
<div>
|
||||
<span>{licenses.map(license => license.id.slice(-4)).join(',')}</span>{' '}
|
||||
<Tooltip content={_('xostorMultipleLicenses')}>
|
||||
<Icon color='text-danger' icon='alarm' />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const license = licenses?.[0]
|
||||
|
||||
return license !== undefined ? (
|
||||
<span>{license.id.slice(-4)}</span>
|
||||
<span>{license?.id.slice(-4)}</span>
|
||||
) : (
|
||||
<form className='form-inline'>
|
||||
<SelectLicense onChange={this.linkState('licenseId')} productType='xostor' />
|
||||
<ActionButton
|
||||
btnStyle='primary'
|
||||
className='ml-1'
|
||||
disabled={licenseId === 'none'}
|
||||
handler={this.bind}
|
||||
handlerParam={licenseId}
|
||||
icon='connect'
|
||||
>
|
||||
{_('bindLicense')}
|
||||
</ActionButton>
|
||||
</form>
|
||||
<div>
|
||||
{license !== undefined && (
|
||||
<div className='text-danger mb-1'>
|
||||
<Icon icon='alarm' /> {_('licenseHasExpired')}
|
||||
</div>
|
||||
)}
|
||||
<form className='form-inline'>
|
||||
<SelectLicense onChange={this.linkState('licenseId')} productType='xostor' />
|
||||
<ActionButton
|
||||
btnStyle='primary'
|
||||
className='ml-1'
|
||||
disabled={licenseId === 'none'}
|
||||
handler={this.bind}
|
||||
handlerParam={licenseId}
|
||||
icon='connect'
|
||||
>
|
||||
{_('bindLicense')}
|
||||
</ActionButton>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user