Compare commits

..

1109 Commits

Author SHA1 Message Date
Pierre Brunet
9829eb58f3 fix: add debug to better understand timeout error 2026-08-07 15:53:52 +02:00
Mathieu
2e5ec8c9e2 fix(xo-server): auth provider did not wait for token update (#10233) 2026-08-06 15:24:21 +02:00
Mathieu
8054d88a88 fix(xo-server): handle 'no-bundle' as legacy licence (#10241) 2026-08-06 15:22:22 +02:00
Thierry Goettelmann
20bedca539 refactor(web-core,lite,web): migrate from modal/drawer packages to the unified overlay system (#10096) 2026-08-05 17:13:15 +02:00
Grandalf
f61019b960 fix(rest-api): coerces strings into numbers in external-router when type="number" (#10213) 2026-08-05 16:22:09 +02:00
Thomas Moraine
be960b36b9 docs(advanced.md) fix broken link to kubernetes page (#10230)
The docs on devops tools and kubernetes was moved
from the Vates VMS doc to the Xen Orchestra doc.
As a result, one link to the kubernetes page broke in the XO doc.
This commit fixes that broken link.

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2026-08-05 09:40:12 +02:00
Mathieu
40dede9e11 feat: release 6.7.1 (#10229) 2026-08-04 15:42:40 +02:00
Mathieu
e9548959c6 feat: technical release (#10228) 2026-08-04 12:05:18 +02:00
Florent BEAUCHAMP
be94d0d083 fix(xo-server/v2v): incorrect uuid for running VM without snapshot (#10194)
* add more details in task log
* fix an issue with VM with more than 10 snapshot, add unit test for this parsing
* fix the real bug: vmware do not update the vmdk description file before the next VM reboot. It means that the uid of the snapshot and vm are the same, thus finishing the transfer with " nothing to transfer" . We are now using the path on source as key + add unit test + add compatibiliy with legacy VM migrated with the uid.
* add e2e test for v2v
2026-08-04 10:41:07 +02:00
Julien PIALET
9c6d10327f doc(RBAC) : added details on POST privilege example (#10168) 2026-08-03 15:20:20 +02:00
spacotte-vates
a94eba4dd1 fix(nbd-client): fix long datamap read nbddisk (#10157)
Customer imported large VM from VMWare (> 3 To), import switched to Qcow2 automatically but the process was stuck while building the qcow2 L1/L2 address tables, where the NbdDisk's hasBlock is called for each l2 table entry which iterated over the disk dataMap which contained ~113,528 ranges. This froze the process for more than 18 minutes after which it was either killed or died due to OOM error.

This fix improves the NbdDisk by:

    Updating processDatamap to throw on overlapping ranges and merge touching extents.
    Override default getBlockIndexesCount with optimized Nbd one.
    Improve hasBlock with a built in cursor to improve performance.

Test file added to cover the new function and updated code.
2026-08-03 11:24:44 +02:00
Florent BEAUCHAMP
a6970b0150 fix(backups/filerestore): restore files from proxy as tgz (#10208)
from ticket 59505

node-tar >= 7 returns a Minipass stream: it has `pipe()` but is not a `node:stream`
instance. xo-proxy serves this stream through Koa, which pipes only bodies passing
instanceof Stream` and JSON-serializes anything else — so a file restore through a
proxy answered a JSON dump of the tar object instead of the archive. Wrapping it in a
real node stream also propagates tar errors to the consumer and aborts the tar job
when the client disconnects.
2026-08-02 14:57:48 +02:00
Bastien Nollet
26cd23179d fix(load-balancer): don't get disabled hosts (#10209)
The load balancer did not take into account that hosts may be in disabled state, which means it's impossible to migrate a VM to it.
This PR fixes that by ignoring these hosts instead.

Fixes ticket 62077
2026-08-02 14:56:58 +02:00
Joris K
03d2af39ad feat(xo6): add host scan PIFs button and job (#10191)
* changelog

* feat(xo6): add host scan PIFs button and job

* add changelog

* missing translation

* review

* review

---------

Co-authored-by: MathieuRA <contact@mathieu-raisin.fr>
2026-07-31 16:10:46 +02:00
Mathieu
be868a307b feat(rest-api): add scan-pifs endpoint (#10187)
* feat(rest-api): add scan-pifs endpoint

* changelog

* add pifs tag to the route

* fix typo

* fix  typo in changelog

* fix

* missing response
2026-07-31 11:50:05 +02:00
Florent BEAUCHAMP
01c00b110e test(immutable-backups): integration test of the new process 2026-07-31 11:28:25 +02:00
Florent BEAUCHAMP
65c32014d6 feat(immutable-backups): better cleanup on start
feat: changelog
2026-07-31 11:28:25 +02:00
Florent BEAUCHAMP
69dfeeaaf6 fix(immutable-backups): ensure shell is in english 2026-07-31 11:28:25 +02:00
Florent BEAUCHAMP
4a1269b898 feat(immutable-backups): run lift on proces start 2026-07-31 11:28:25 +02:00
Marie Garde
498cc7509f feat(xo6/host): host action button start shutdown (#10088)
* feat(xo6): add disable and enable host button

* Add button enable disable

* remove route map file

* Refacto for PR

* Add modal

* refacto for PR

* change key i18n

* fix i18n

* fix route map

* change modale to VtsActionModale

* refacto for MR

* fix type VtsActionModal

* refacto for MR

* add enable disable un treeview

* refacto treeview

* wip

* add shutdown and start button and job

* Update changelog and remove route map

* add check on disable button

* fix job error type

* update button for menuitem

* wip i18n

* add vtsaction modal

* tests ok

* fix build

* refacto for mr

* fix fr.json

* refacto for PR

* add check pending action

* add check host not halted

* fix enable disable button while host shutdown

* returns from MR

* fix changestate host header

* wip

* wip

* add check pending operations

* last fix for PR

* fix menuItem in mobile vue

* remove route map

* returns from MR

* fix start button

* Add changelog
2026-07-31 10:12:52 +02:00
Florent BEAUCHAMP
f98b4c9e4a docs(s3): add ovh as a tier 1 provider (#10206) 2026-07-31 09:40:37 +02:00
Mathieu
cc7ba53b29 feat(lite): 0.24.0 (#10204) 2026-07-30 17:46:20 +02:00
Mathieu
1a795970f9 feat: release 6.7.0 (#10203) 2026-07-30 17:26:51 +02:00
Mathieu
59306dce8f feat: technical release (#10198) 2026-07-30 11:08:08 +02:00
Florent BEAUCHAMP
6ba465be04 fix(xapi): don't try to compute progress on Buffer (#10195)
* fix(xapi): don't try to compute progress on Buffer

introduced by 76b45a9b3b

* port type to vates/types
2026-07-30 10:19:37 +02:00
Mathieu
068c4cdc86 feat: technical release (#10192) 2026-07-29 15:30:14 +02:00
Mathieu
93a7c3b509 fix(docs): fix yarn.lock and broken anchor (#10190)
Introduced by ada3cf745
2026-07-29 14:52:45 +02:00
Florent BEAUCHAMP
07c42bbd1e fix(fs/s3): improve delete multiple performances (#10189) 2026-07-29 11:29:55 +02:00
Florent BEAUCHAMP
ada3cf745c feat(xo-server): serve doc from xo + add backup calculator (#10154)
* feat(xo-server): serve doc locally
* the doc is already distributed from the mono repo
* add a build phase to the global yanr build that build a doc with the
right prefix
* add a http mount doc => embed build

* doc(backups): add a space calculator
* estimate the space needed for backups
* compute the duration protected by immutability
2026-07-29 09:48:41 +02:00
Mathieu
6a0332a784 fix(xapi/host): fix async use in a sync block for smart reboot (#10180)
* fix(xapi/host): fix async use in a sync block for smart reboot
2026-07-29 09:38:38 +02:00
Mathieu
09bd5c3425 feat(openmetrics): expose SR content and UUID (#10149)
Refs XO-2503
2026-07-29 09:24:34 +02:00
Florent BEAUCHAMP
26c36d485e fix(backups): don't hide errors during backup transfers (#10183) 2026-07-28 17:08:19 +02:00
Thierry Goettelmann
087fd5d397 feat(overlay): allow manually aborting overlays (#10167) 2026-07-28 16:47:45 +02:00
Grandalf
4636fcf820 feat(types): type-register-rest-routes (#10171) 2026-07-28 14:51:36 +02:00
Marie Garde
dd58fed217 feat(xo6/xolite): add copyall button xo and lite (#10081) 2026-07-28 13:38:57 +02:00
vates-bot
4aa8a31c25 feat(web-stack/i18n): update Xen Orchestra 6 (#10033) 2026-07-28 11:00:31 +02:00
Olivier Lambert
4ca263c10a feat(xo-server,xo-web/rpu): shut down and restart pinned VMs instead of aborting (#10125)
* feat(xo-server/rpu): opt-in shutdown/restart of pinned VMs instead of aborting
2026-07-28 10:05:43 +02:00
Alina D
8d58147d66 feat(xo6): align vertically all hosts in Treeview (#10153) 2026-07-28 09:53:07 +02:00
Joris K
5849844fca fix(lite): change defer type import on vm creation (#10176) 2026-07-28 09:22:52 +02:00
Mathieu
a5648aede8 fix(web-core): fix several store issues (#10156) 2026-07-28 09:22:45 +02:00
Florent BEAUCHAMP
84ce89d199 fix(disk-transform): implement a guardrail to ensure it does not read block only in parent 2026-07-27 19:45:25 +02:00
Florent BEAUCHAMP
9140cd273b fix(disk-transform): fix getBlockIndexesCount
introduced by #10058

the current method assumes that source block are full but they can
be sparse. In the worst case getBlockIndexesCount can be the same as
the source
2026-07-27 19:45:25 +02:00
Florent BEAUCHAMP
e710135eda fix(backups): ensure replication never transmit a delta without a base
differentiate when it's expected to transmit a full , and throw an
error on any discrepency
2026-07-27 19:45:25 +02:00
Florent BEAUCHAMP
5509f62ccd fix(backups): fix aggregated backup failing instead of falling back to a full
map.foreach((value,key)=> , not map.foreach(key=>
2026-07-27 19:45:25 +02:00
Florent BEAUCHAMP
6c4f7336e1 fix(backup-archive): really run tests 2026-07-27 19:45:25 +02:00
Florent BEAUCHAMP
65cd607a49 fix(backups): force a full on continuous replication if the base could have been empty 2026-07-27 19:45:25 +02:00
Florent BEAUCHAMP
453ffc98d2 fix(backups): fall back to a full backup if it's possible that the base is broken 2026-07-27 19:45:25 +02:00
Florent BEAUCHAMP
f8df78c03d feat(backup-archive): add method to get the root of a chain 2026-07-27 19:45:25 +02:00
Florent BEAUCHAMP
3613b30aa1 fix(backups): stamp backup after the fix 2026-07-27 19:45:25 +02:00
Florent BEAUCHAMP
58332e6b16 doc(backups): more clarification on qcow2 + nbd 2026-07-27 19:45:25 +02:00
Florent BEAUCHAMP
9388fa4bb6 fix(disk-transform): erronous offset in DiskLargerBlock when copying data
the offset must be relative to the start of THIS large block, not to the
start of the disk — otherwise every large block after index 0 lands past
the end of destinationBlock and copy() silently writes nothing (zeroed block).

affect qcow2 incremental backup with NBD disable

NBD + qcow2 and Vhd backups don't go through this code path
2026-07-27 19:45:25 +02:00
Olivier Lambert
f683817dec feat(docs): working cookieless Matomo tracking on a dedicated site
The previous static/js/matomo.js snippet never loaded the tracker
script, so nothing was ever recorded; it also pointed at site 5, the
XO website. Replace it with the client module from the Vates VMS docs
on the new dedicated docs site (23): cookieless page-view tracking that
follows SPA navigations, and instrumentation of the lunr search box
reporting each settled query with its suggestion count, so zero-result
searches surface missing content. The feedback widget mirrors votes and
thumbs-down reasons as doc_feedback events.

Also fills in the Formbricks IDs for the "XO Docs page feedback"
survey, enabling the feedback widget.
2026-07-27 19:25:51 +02:00
Olivier Lambert
51fe6b8920 feat(docs): page feedback widget wired to Formbricks
Swizzles DocItem/Footer to show a 'Was this page helpful?' widget under
every doc article, ported from the Vates VMS docs. Votes post to the
self-hosted Formbricks instance via its public client API; a thumbs-down
asks why (outdated, unclear, missing info, inaccurate) plus optional
free text. The vote is recorded on click so it survives the visitor
leaving before the follow-up. Page path rides along as a hidden field
for per-page filtering.
2026-07-27 19:25:51 +02:00
Mathieu
74b952be2b feat: technical release (#10174) 2026-07-27 15:05:16 +02:00
Florent BEAUCHAMP
efbfb664f7 fix(backup-archives): regenerate cache on discrepencies (#10132)
* ensure cache is generated completely at the end of the backup job
* ensure a broken cache is regenerated even without merge during the cleanup phase
* ensure a broken cache is regenerated on request
2026-07-27 14:19:17 +02:00
Bastien Nollet
1616ec6f41 feat(backups): make fallback cause appear in the task (#10148) 2026-07-27 14:10:03 +02:00
Alina D
246e2e4eba feat(xo6/pool): add connect/disconnect actions on page site (#10075) 2026-07-27 13:26:20 +02:00
Mathieu
bcb91e8b4f feat(rest-api): add POST /srs route to create a storage repository (#9990) 2026-07-27 10:37:55 +02:00
Florent BEAUCHAMP
76b45a9b3b fix(xo-server): ensure import task progress is correctly updated (#10133) 2026-07-27 10:23:29 +02:00
Sylvère
5f42e437c7 feat(lite): add SR connect/disconnect/delete (#10006) 2026-07-27 09:44:23 +02:00
Sylvère
b76294a1b1 feat(web-core): refactor the sidebar system (#10120) 2026-07-27 09:43:16 +02:00
Grandalf
b7bce8e7e7 fix(csp): update Content-Security-Policy to allow connections to GitH… (#10162) 2026-07-27 09:03:17 +02:00
Pierre Brunet
1445f986cf feat: add azure section (#10166)
* feat: linter

* feat: add Azure doc
2026-07-24 17:59:06 +02:00
MlssFrncJrg
83b43a44f6 feat(xo5/hub): open Hub to all plans and gate recipes individually (#10117) 2026-07-24 17:30:04 +02:00
Olivier Lambert
235ff3c455 feat(xo6/new-vm): set HA restart priority at VM creation (#10072) 2026-07-24 14:15:22 +02:00
Alina D
b0194b2c5f feat(xo6): update input version (#10009) 2026-07-24 11:47:22 +02:00
Marie Garde
316122a683 feat(xo6/users): Add Users Table (#10029) 2026-07-24 10:34:04 +02:00
Sandrine Barrucand
954c0aaa9e fix(xo6): add separator between label and hint in table actions (#10143) 2026-07-23 16:19:37 +02:00
Sandrine Barrucand
377e315e02 fix(xo6/host): wrap Network name in PIF side panel (#10155) 2026-07-23 14:54:56 +02:00
Joris K
9b6f882424 fix(xo6/lite): fix some design inconsistency between pages on xo6 and lite (#10109) 2026-07-23 14:23:37 +02:00
Marie Garde
874da48125 feat(xo6): add enable/disable host button (#10074) 2026-07-23 12:02:46 +02:00
Pierre Brunet
e22ee2547b Fix: avoid default value for "private" in normalize-packages script (#10121) 2026-07-23 11:43:21 +02:00
Mathieu
43e45667ab fix(xo-server/rpu): LINSTOR packages update progress no longer exceeds 100% (#10122) 2026-07-23 11:24:23 +02:00
Florent BEAUCHAMP
c05f9bb357 feat(backup): update footer and header at once (#10123) 2026-07-23 11:16:56 +02:00
amouillard-vates
92f8ed4077 feat(lite): add copy info into JSON action to pool networks table (#10083) 2026-07-23 11:10:52 +02:00
MathieuRA
fc6415b4ec fix(xo-server): fix group deletion checking RBAC authorization 2026-07-23 10:52:29 +02:00
MathieuRA
53014d7d18 feat(rest-api): expose acl-roles/:id/groups 2026-07-23 10:52:29 +02:00
MathieuRA
5bb4ac62bf fix(xo-server): delete user don't remove the link between user and acl-role 2026-07-23 10:52:29 +02:00
MathieuRA
b4433e4ecc feat(rest-api): expose acl-roles/:id/users 2026-07-23 10:52:29 +02:00
MathieuRA
fee9bf8334 feat(xo-server): add groupIds and userIds to acl-role object 2026-07-23 10:52:29 +02:00
MathieuRA
1455801282 feat(xo-server): add aclRoleIds property to groups 2026-07-23 10:52:29 +02:00
MathieuRA
bd5937366a feat(rest-api): expose groups/:id/acl-roles 2026-07-23 10:52:29 +02:00
Joris K
d2667ac64b feat(xo6/lite): updated the modal so it can be closed using the X or by clicking outside it (#10137) 2026-07-23 09:06:19 +02:00
Pierre Brunet
de9687e2c0 fix: issue on file overwriting during vhdDirectory creation (#10139) 2026-07-22 15:04:00 +02:00
Florent BEAUCHAMP
c12bd807ed feat(qa-test): add a script to do a load test (#10119)
add new option and script to launch a load test :

    number of vm is configurable
    concurrency is configurable
    number of backup run is configurable

will do multiple run, then a restore
2026-07-22 14:36:50 +02:00
amouillard-vates
7b21690721 feat(xo6/pool): add new VM button and disconnect to pool tree actions (#10046) 2026-07-22 14:19:26 +02:00
Sandrine Barrucand
b719c0a018 refactor(xo6/vif): merge connect/disconnect buttons into a single component (#10112) 2026-07-22 13:25:45 +02:00
Grandalf
8e8afeb1b5 feat(xo-server): add content security policy (#10101) 2026-07-22 12:15:16 +02:00
Elise-F
0a5693345f feat(traffic rule type by VIF): display only the traffic rules for VMs and disable VMs without a VIF. (#9977)
- Show only traffic rules of VMs. Don't include `vm-snapshot` or `vm-template`
- For VM selection, display all VMs and disable those without a VIF, with a
message displayed
2026-07-22 11:01:17 +02:00
Bruno Verachten
ebce0e508f docs(xo): document VBD attach and note ISO mount is JSON-RPC only (#9981) 2026-07-21 16:35:13 +02:00
Olivier Lambert
64f1327f46 feat(rest-api): allow setting HA restart priority on VM creation (#10070)
See #10069
2026-07-21 16:02:08 +02:00
bleader
d2c28450d4 docs(sdn_controller.md): clarify openssl3 update instructions (#9821) 2026-07-21 11:45:09 +02:00
MlssFrncJrg
4eb43fbc36 feat(xo6): resolve objects in tasks names (#9830) 2026-07-21 10:37:36 +02:00
Bastien Nollet
e44034285b fix(backups): fix failed status on successful retry (#10129)
A VM backup log status is calculated depending on its subtask status. Previously, we made that it would be a failure if at least one of its subtask is a failure.

However, in case of successful retries, the intended behaviour is to have failed subtasks, which should not affect the parent task status. The first commit of this PR fixes that.

The second commit simplifies the getStatus function and its usage.
---------

Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@vates.tech>
2026-07-21 09:50:07 +02:00
Thierry Goettelmann
9c1aa104f5 chore(deps): hoist pinia, vite and vue-router to root devDependencies (#10138) 2026-07-21 09:30:21 +02:00
Thierry Goettelmann
4ff8af838f feat(web-core): add hide-permanently package (#10108) 2026-07-21 09:28:07 +02:00
amouillard-vates
101fb46ad1 fix(xo6/site): remove site tree view new vm action (#10135) 2026-07-21 09:04:44 +02:00
Thierry Goettelmann
687dd5f456 feat(web-core): add promise-based overlay system (#10095) 2026-07-20 17:34:09 +02:00
Mathieu
ee6c6b3bbc feat(xo-server): delay load balancer re-enable after RPU (#10111)
XO-2706
2026-07-20 16:51:45 +02:00
Mathieu
54260268ba fix(xo-server): demote successful fallback logs (#10131)
XO-2734
2026-07-20 16:00:13 +02:00
Elise-F
0d53d7621a feat(lite): add the “connect/disconnect” action for a VIF on a VM (#10080) 2026-07-20 15:12:46 +02:00
Florent BEAUCHAMP
c3a2e89c1f feat(disk-cli): add option to show chain info, size, and improve peformances (#10063) 2026-07-20 14:37:20 +02:00
Enée DI IORIO
27fe714e17 feat(web-core): update UiTextArea to V5 (#9855) 2026-07-20 11:10:33 +02:00
Mathieu
5e6c4ad96d fix(xo-server): automatically reconnect to XenServer after connection (#10086)
A server left enabled but without a Xapi instance was never retried by anything in
xo-server. Three paths lead to that state:

    a failed explicit connect: xo-server boots while a pool is down, a user clicks
    "connect" during an outage, or the host-eject path exhausts its 5 attempts;
    a SESSION_INVALID teardown after a pool master restart (_sessionCall calls
    disconnect(), and nothing listens to server:disconnected);
    fixing a server's credentials/address while it is disconnected (server.set never
    triggers a connection).

Once there, the server stays disconnected forever, showing an error that can be
minutes or hours old. Support ends up chasing false "pool is dead" reports, typically
right after a rolling pool update, which is exactly when hosts reboot by design.
2026-07-20 09:42:12 +02:00
Florent BEAUCHAMP
926eb348d3 feat(xo-server): log more data to track memory leaks (#10041)
* feat(xo-server): log more data to track memory leaks

* changelog
2026-07-17 16:35:06 +02:00
Mathieu
bfb6b15cfa fix(xo-server/rpu): retry check_update while updater plugin is busy (#10115) 2026-07-17 16:17:09 +02:00
Grandalf
d05be427fb fix(xo-server): replace hasPermissions (#10093) 2026-07-17 11:48:37 +02:00
Pierre Brunet
76fa4bba51 fix(CI): do not log "ENOENT" when unlink fails after check (#10102)
* fix(CI): do not log "ENOENT" when unlink fails after check

* fix: await for mkdirp before continuing
2026-07-17 10:16:07 +02:00
Grandalf
d02098a397 fix(xo-server): don't log the admin password on server creation (#10094)
XO-2647
Introduced by #3579
2026-07-17 09:43:51 +02:00
Sandrine Barrucand
20c9ed8a41 feat(xo6/vm): add New VM button on Host view (#10048) 2026-07-16 16:18:31 +02:00
Mathieu
2f19fda3a5 feat(xo-server/rpu): assert each host can be evacuated right before its evacuation (#10097) 2026-07-16 15:16:17 +02:00
Mathieu
798bdeb973 fix(xo-server): disable auto power on during rolling pool update/reboot (#10104) 2026-07-16 14:34:50 +02:00
MlssFrncJrg
ac52f8b75e fix(perf-alert): update URL generation for alarms to support v6 routing (#10054)
* fix(perf-alert): update URL generation for alarms to support v6 routing

* changelog entry

* fix changelog

* Apply suggestions from code review

Co-authored-by: Mathieu <goore.csmoviz@gmail.com>

---------

Co-authored-by: Mathieu <goore.csmoviz@gmail.com>
2026-07-16 10:25:24 +02:00
Enée DI IORIO
691af588d3 feat(Lite): remove all coming soon (#10068) 2026-07-15 17:26:22 +02:00
Grandalf
cda3c3a724 perf(xo-server): don't wait for errored br to send data (#10025) 2026-07-15 15:32:21 +02:00
John P. Cooper
f2d6b7f0e4 Update XCP-ng version label in supported_hosts.md to bring into sync with supported status (#10092) 2026-07-15 15:20:56 +02:00
Sandrine Barrucand
1afffb8f75 feat(xo6): add possibility to export VM (#9989) 2026-07-15 14:42:36 +02:00
Mathieu
2b7b6baf02 feat(xo-server-openmetrics): estimate per-VM power consumption from CPU (#10031)
* feat(xo-server-openmetrics): estimate per-VM power consumption from CPU
usage

* refactor(xo-server-openmetrics): move hostPowerWatts undefined guard to
caller

* fix(xo-server-openmetrics): log IPMI power read failure as info
2026-07-15 14:06:49 +02:00
Florent BEAUCHAMP
0cfcb998a2 feat(disk-transform): port getBlockIndexesCount to the relevant classes (#10058) 2026-07-15 14:04:53 +02:00
Sandrine Barrucand
3ffd9b1f56 feat(xo6/vm): in the VM actions, add a separator to Delete, placeholder actions and submenu (#10036) 2026-07-15 13:18:34 +02:00
Mathieu
184d65cef1 feat(xo-server): add crash-resistant on-disk observability for RPU/RPR (#10078)
* feat(xo-server): add crash-resistant on-disk observability for RPU/RPR
(XO-2640)

* fix(xo-server): reconcile RPU heartbeats left pending at boot (XO-2640)

* chore(CHANGELOG): add RPU observability entry (XO-2640)

* docs(rpu): document on-disk traces and heartbeat for troubleshooting

* refactor(xo-server): address RPU observability review feedback
2026-07-15 12:23:36 +02:00
Elise-F
a7ba7c6a89 feat(xo6): add VM duplicate action (#9580) 2026-07-15 12:16:34 +02:00
Sandrine Barrucand
25da6d4c4b fix(xo6/xolite): switch CPU and RAM panels (#10059) 2026-07-15 11:47:20 +02:00
amouillard-vates
8b78ab985d feat(xo6/site): add new VM button and connect pool to site tree actions (#10047) 2026-07-15 11:45:19 +02:00
Joris K
2bee8c279d feat(xo6): add possibility to create VIF (#9677) 2026-07-15 11:26:42 +02:00
Bastien Nollet
5de65620f1 fix(xapi-explore-sr): fix crash on unreferenced VDI (#10113)
Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@vates.tech>
2026-07-15 11:20:33 +02:00
Olivier Lambert
b078d3d42d feat(xen-api): reconnect to a surviving pool member when the master is unreachable (#10016)
* feat(xen-api): reconnect to a surviving pool member when the master is unreachable

XO follows a pool master change only through HOST_IS_SLAVE redirection, which
needs the contacted host to be up and answering. That covers a graceful master
hand-over (the old master stays up as a slave and redirects us), but not the
case where the master host itself becomes unreachable while another host takes
over as master: HA automatic failover, or a manual emergency transition. The
stored URL then points at a host that no longer responds, login fails with a
transport error instead of HOST_IS_SLAVE, the existing retry never redirects,
and the connection stays stuck on the dead master.

Maintain a set of pool-member addresses (seeded from the configured target and
optional opts.candidateHostnames, refreshed from host.get_all_records on every
session open) and, when the current target is unreachable at the transport
level, probe the other members in parallel. The first to answer wins; if it is
a slave, its login redirects us to the current master through the existing
HOST_IS_SLAVE retry. XapiError responses (including auth failures) are not
failed over, so wrong credentials still fail fast.

* feat(xo-server): persist pool-member addresses so master failover survives a restart

The xen-api failover relies on an in-memory set of pool-member addresses, which
is lost when xo-server restarts: it then only knows the configured host. That is
exactly the address that is dead when XO runs on the very pool whose master just
failed and was HA-restarted onto a survivor, leaving XO unable to reconnect.

Persist the member addresses on the server record (Redis, JSON-encoded like the
existing error field), refresh them whenever the pool object is seen via the
xen-api candidateHostnames getter, and feed them back as opts.candidateHostnames
when (re)building the connection. After a restart the connection can then probe
the surviving members and reconnect to the new master.

* chore(changelog): reconnect to a surviving pool member on master failure

Add the user-facing changelog entry and list xen-api in the packages to
release for the master-failover reconnect feature.

* fix(xen-api): prune ejected hosts from pool-member failover candidates

host.get_all_records is authoritative current membership, so replace the
candidate set on refresh instead of unioning into it. An ejected host is
typically still alive as its own standalone master with the same
credentials, so leaving it in the set could make a failover probe
silently connect to the wrong pool. The currently-connected target is
preserved since host.address may differ from the address XO used to
reach it.

* fix(xen-api): harden surviving-member failover per review

- reject failover probes on sessionId-based connections (no reusable
  credentials to open a session on another host)
- bracket bare IPv6 addresses (host.address / HOST_IS_SLAVE) before using
  them as URL hostnames
- log the probe session out even when the host answers after the probe
  timeout, to avoid leaking a session
- abort the mid-session failover loop when the connection was torn down or
  replaced while waiting (e.g. user disconnected the server)
- xo-server: drop persisted pool-member addresses when the connection
  identity (host/credentials) changes, so a stale slave list can't send a
  failover to the wrong pool

See #10016
2026-07-15 11:04:16 +02:00
Bastien Nollet
913148ec2f chore(changelog): add previously unreleased package (#10106) 2026-07-13 10:48:01 +02:00
Mathieu
44e5ba4cf5 feat(rest-api): add PATCH /rest/v0/vdis/{id} (#9945) 2026-07-10 15:40:05 +02:00
Mathieu
db795ccfe2 feat(rest-api): ability to set transient for host.disable (#10040) 2026-07-10 15:33:08 +02:00
MlssFrncJrg
519b2bd7e1 feat(rest-api): add RBAC check for pool endpoints (#9891) 2026-07-10 15:27:26 +02:00
Grandalf
b5f5e39ddc feat(rest-api): add array type to FieldDefinition (#10055) 2026-07-10 14:33:00 +02:00
Alina D
f3f8519b7a fix(xo6): repair negative other value in backup repository dashboard (#10044) 2026-07-10 09:38:42 +02:00
Bastien Nollet
1d82c373da feat: release 6.6.2 (#10105) 2026-07-09 16:50:11 +02:00
Bastien Nollet
12b380e806 feat: technical release (#10103)
* feat(@xen-orchestra/fs): 4.9.2

* feat(@xen-orchestra/backup-archive): 1.0.4

* feat(@xen-orchestra/backups): 0.73.7

* feat(@xen-orchestra/proxy): 0.31.2

* feat(xo-server): 5.205.2

* chore(CHANGELOG): update next
2026-07-09 14:31:09 +02:00
Pierre Brunet
f8c9c66c40 fix(backups): remove warning for encrypted xva (size cannot be checked for encrypted data) 2026-07-09 12:00:48 +02:00
Pierre Brunet
1afa1e52ed fix(backups): transfer length and maxStreamLength in pipeline() 2026-07-09 12:00:48 +02:00
Florent BEAUCHAMP
b2a48ffacc fix(fs): compute the output stream precisely when encrypted (#10061)
* fix(fs): compute the output stream precisely when encrypted

the current encryption add 12 byte of initialization vector at
the start + 16 bytes of authentication tag at the end

thus, the destination stream can be bigger than the source stream,
and this length is used to compute the optimal chunk size for s3/
azure

from https://xcp-ng.org/forum/topic/12328/error-mirroring-full-backups-to-backblaze-b2

---------

Co-authored-by: Pierre Brunet <pierre.brunet@vates.tech>
2026-07-09 10:35:40 +02:00
Bastien Nollet
a3077cdb62 feat: release 6.6.1 (#10087) 2026-07-08 09:56:44 +02:00
Bastien Nollet
3ba9523dfe feat: technical release (#10084) 2026-07-07 14:37:05 +02:00
spacotte-vates
377581b44a fix(backups): add warn with debug for snapshot pileup issue (#10076) 2026-07-06 14:56:03 +02:00
Thomas Moraine
090ce7e721 docs(xo): refactor navbar navigation to create a unified documentation ecosystem (#10067)
* docs(xo): overhaul navbar to create a unified doc ecosystem

- Clean up the navbar by dropping the redundant GitHub link (retained in footer).
- Relocate "Blog" to the "What's new in XO 6" sidebar.
- Introduce direct navbar links for XCP-ng and Vates VMS.
- Rename "Documentation" to "Xen Orchestra".

This aligns the navigation layout across our three documentation sites, allowing readers to smoothly transition between ecosystems with a fully unified experience.

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>

* docs(xo): move home link from header to footer

To make the navbar cleaner and make space
for the links to the other documentation websites
We're moving the Home link from the header to the footer
and renaming the link to 'About Xen Orchestra'.
Also, we're displaying the 'What's new in XO 6' section again
which now contains a link to the XO blog

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2026-07-03 10:30:40 +02:00
Thomas Moraine
d53dd3e990 docs(xo): import devops tools documentation from vates vms (#10062) 2026-07-03 09:24:44 +02:00
Thomas Moraine
1b0dbc7d3b docs(xo6): hide categories that dont contain any actual pages (#10066)
Recently we've introduced new sections for the xo6 category of the XO doc.
These categories were meant to be filled later with new pages.
The pages were created as placeholders, but didn't actually contain any text, just titles.
These made readers confused, so this commit
Hides the sections from the sidebar until we update them
With actual content.

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2026-07-03 08:44:14 +02:00
Florent BEAUCHAMP
bb4daac07a fix(xo-server): store rrd as Float32 instead of string (#10039) 2026-07-02 15:57:43 +02:00
spacotte-vates
63f8d624fd fix(backups): improve resume of backup merge failure (#10053)
Introduced by #9765

When a backup merge fails on a VHD file, the footer might be missing. This caused a footer1 !== footer2 error and triggered a full backup while not deleting the previous chain and resulted in all further backup jobs to be displayed as failures even though the new backup chain was working properly.

The fix allows the cleanVm to resume the merge where it stops even if the VHD file footer is missing.
2026-07-01 17:01:38 +02:00
Alina D
373a4c8c6d fix(web-core): make VDI used space in progress bar appear correctly (#10027)
Used space progress bar now shows correctly filled at 100% instead of appearing half-filled

References issue :  XO-1696
2026-07-01 10:43:37 +02:00
Warren Postdam
1ec57392fc docs: update users to add passkeys auth (#9975)
Add configuration elements to allow FIDO2 (passkeys) administrators to configure
with XO by using `auth-oidc` plugin.
2026-07-01 10:24:55 +02:00
Joris K
376bb3603f fix(xo6): fixed an issue where the selected item in the treeview would scroll to the top (#10052) 2026-07-01 09:02:12 +02:00
Bastien Nollet
3bc70f657d feat(lite): 0.23.0 (#10050) 2026-06-30 16:50:07 +02:00
Bastien Nollet
f3b2ac5983 feat: release 6.6.0 (#10049) 2026-06-30 16:20:58 +02:00
Bastien Nollet
eb4c55d82c feat: technical release (#10045)
* feat(@vates/types): 1.28.0

* feat(@xen-orchestra/acl): 1.4.0

* feat(@xen-orchestra/backups): 0.73.5

* feat(@xen-orchestra/proxy): 0.31.0

* feat(@xen-orchestra/rest-api): 0.35.0

* feat(@xen-orchestra/web): 0.55.1

* feat(xo-server): 5.205.0

* feat(xo-server-sdn-controller): 1.4.0

* chore(CHANGELOG): update next

* clear changelog
2026-06-30 12:01:18 +02:00
spacotte-vates
d43ca09b8b feat(rest-api): add 7 rest actions on hosts (#9887) 2026-06-30 11:37:17 +02:00
Grandalf
3ad3ecb244 feat(sdn-controller): add patch routes for vif and network (#9936) 2026-06-30 11:29:29 +02:00
Bastien Nollet
7bbe139b36 fix(backups): fix memory error during delta replication (#10034) 2026-06-29 17:25:17 +02:00
spacotte-vates
235fc07404 feat(rest-api): backup-repository health/bench routes (#9847) 2026-06-29 17:12:51 +02:00
Bastien Nollet
26038fdaec feat: technical release (#10037)
* feat(@xen-orchestra/backups): 0.73.4

* feat(@xen-orchestra/web-core): 0.56.0

* feat(@xen-orchestra/proxy): 0.30.5

* feat(@xen-orchestra/vmware-explorer): 0.14.0

* feat(@xen-orchestra/web): 0.55.0

* feat(xo-server): 5.204.0

* feat(xo-server-netbox): 1.13.1

* feat(xo-web): 5.199.0

* chore(CHANGELOG): update next
2026-06-29 14:38:39 +02:00
Mathieu
4fe505b229 fix(web-core): required disappear after clicking on VtsSelect (#10032) 2026-06-29 14:04:20 +02:00
Sylvère
276c4e5f7e feat(lite): add storage tabs and read-only SR views (#10005) 2026-06-29 13:31:52 +02:00
Florent BEAUCHAMP
91badc593c fix(@xen-orchestra/proxy): invalid parameter in listVmBackups (#10035)
introduced by #9143
2026-06-29 13:01:45 +02:00
Pierre Brunet
130b1612e6 fix(xo-server): add safeguards to xo-server shutdown and update changelog 2026-06-29 10:55:29 +02:00
Pierre Brunet
e8c43dafce fix(s3): stop s3 agents properly after xo-server shutdown 2026-06-29 10:55:29 +02:00
Pierre Brunet
8e0c0e05c0 feat(xo-server-auth-ldap): support LDAP multidomain (#10015) 2026-06-29 10:49:09 +02:00
Enée DI IORIO
71d71b9742 feat(lite): use uiLoader instead of uiSpinner (#10023) 2026-06-29 10:35:45 +02:00
spacotte-vates
34051927a0 fix(encryption): handle missing xen-tools (#9987)
Introduced by 6f68b04

Improve missing xen-tools handling of encryption with better logic and logs.
2026-06-29 09:41:35 +02:00
Sandrine Barrucand
9e6ad45a79 feat(xo6): add possibility to migrate VDI on SR (#9973) 2026-06-29 08:07:21 +02:00
Sandrine Barrucand
52591ec65b feat(xo6/vm): add actions available on VMs tab (#10030) 2026-06-26 17:32:23 +02:00
Mathieu
bf1e992161 feat(rbac): add Storage administrator built-in role template (#9963) 2026-06-26 16:40:52 +02:00
vates-bot
29b9bc46e4 feat(web-stack/i18n): update Xen Orchestra 6 (#9998) 2026-06-26 14:49:43 +02:00
Florent BEAUCHAMP
9d470997e8 fix(xo-server,netbox): properly close hrp stream on error (#10026)
when hrp get a 404, the stream stay ready to be consumed, for example to get
more information on the error

but most of the usage won't handle this case, and will keep the stream in memory
(for example a failing XC patches check) leaking memory

This fixes the calls from xo, and also identifies and fixes the point where we
need body content on 4xx code

relevant code in hrp is here:
https://github.com/JsCommunity/http-request-plus/blob/master/index.js#L149
2026-06-26 14:09:49 +02:00
OlivierFL
836e06843d feat(xo-core): introduce new VtsCardObjectTitle component (#9755) 2026-06-26 12:12:56 +02:00
Joris K
5160ec4c7e fix(web-core/xo6): stream infra tree collections and flatten into a virtualized list (#9986) 2026-06-26 11:33:44 +02:00
Marie Garde
cc88cc395c feat(xo6/vdi): add export VDI action (#9983) 2026-06-26 11:33:14 +02:00
Enée DI IORIO
6ea64e9413 feat(lite): Use web-core's UiLogEntryViewer instead of xo-lite UiRaw (#9995) 2026-06-26 10:28:27 +02:00
Florent BEAUCHAMP
b440f41cd5 fix(xo-server): ignore template memory_static_min is it's less than actual vm memory 2026-06-25 16:29:46 +02:00
Florent BEAUCHAMP
7ea6a5c541 feat(xo-web): show status of previous transfer to help the 2 part transfer 2026-06-25 16:29:46 +02:00
Florent BEAUCHAMP
1987008db1 fix(xo-server): v2v should create a DVDRom is source has one 2026-06-25 16:29:46 +02:00
Florent BEAUCHAMP
4e1e1510e8 fix(xo-server): v2V not updating correctly other config
setting undefined is a no ops, we need to use null to clean a value

this don't have any visible or functionnal impact, but complexify
the build of a filter to look for any non completely migrated VM
2026-06-25 16:29:46 +02:00
Grandalf
e9a8ffe315 feat(xo-server): add esxi.importDisk (#10024)
* feat(xo-server): add esxi.importDisk
2026-06-25 16:17:43 +02:00
OlivierFL
8639e86227 fix: changelog (#10028) 2026-06-25 15:43:26 +02:00
Pierre Brunet
89e1b051cb feat: add failover URIs in ldap domains (#9961) 2026-06-25 14:09:03 +02:00
Florent BEAUCHAMP
c162839d4e docs(immutable-backups): document how to lift the immutability for one VM (#9974) 2026-06-25 11:57:31 +02:00
Enée DI IORIO
d73e9d5e94 chore(lite): remove unused component (#10011) 2026-06-25 10:55:20 +02:00
Sylvère
ab472af351 feat(web-core/web/lite): updated UiPanel to v5 (#9836) 2026-06-25 10:52:35 +02:00
Sylvère
01d8e21538 feat(xo6): warn and link to doc when traffic rules are not in the expected format (#10020)
Co-authored-by: Sylvere <sylvere.armange@vates.tech>
2026-06-25 10:06:35 +02:00
Bastien Nollet
bf2954edb0 fix(backups): uncaught VM error (#10021) 2026-06-25 10:06:19 +02:00
Enée DI IORIO
0fca9b2cde feat(lite): use vtsDivider instead of UiSeparator (#10017) 2026-06-25 10:05:46 +02:00
Alina D
8b9b99b68f feat(xo6): change redirect target (#9984) 2026-06-25 09:22:05 +02:00
Mathieu
c74a83ed06 fix(web): no active job instead no job (#10013) 2026-06-24 16:39:33 +02:00
Bastien Nollet
b96a6dc1e9 feat: technical release (#10022)
* feat(@xen-orchestra/fs): 4.9.1

* feat(@vates/generator-toolbox): 1.1.3

* feat(@xen-orchestra/disk-transform): 1.3.1

* feat(@vates/fuse-vhd): 2.1.3

* feat(xo-common): 0.10.0

* feat(xen-api): 4.7.8

* feat(@vates/types): 1.27.0

* feat(@xen-orchestra/acl): 1.3.0

* feat(@xen-orchestra/backup-archive): 1.0.2

* feat(@xen-orchestra/qcow2): 1.3.1

* feat(@xen-orchestra/xapi): 8.9.0

* feat(@xen-orchestra/backups): 0.73.3

* feat(@xen-orchestra/disk-cli): 2.1.2

* feat(@xen-orchestra/web-core): 0.55.0

* feat(@xen-orchestra/mcp): 1.4.1

* feat(@xen-orchestra/mixins): 0.20.1

* feat(@xen-orchestra/openflow): 0.1.4

* feat(@xen-orchestra/proxy): 0.30.4

* feat(@xen-orchestra/qa-test): 1.1.0

* feat(@xen-orchestra/rest-api): 0.34.0

* feat(@xen-orchestra/upload-ova): 0.1.8

* feat(@xen-orchestra/web): 0.54.0

* feat(xo-acl-resolver): 0.5.3

* feat(xo-server): 5.203.0

* feat(xo-server-audit): 0.15.1

* feat(xo-server-ipmi-sensors): 2.0.1

* feat(xo-server-load-balancer): 0.13.0

* feat(xo-server-netbox): 1.13.0

* feat(xo-server-openmetrics): 1.7.0

* feat(xo-server-sdn-controller): 1.3.3

* feat(xo-web): 5.198.0

* chore(CHANGELOG): update next
2026-06-24 10:18:29 +02:00
Sebastien Marie
a6f2a25aec docs: explain traffic rules migration path (from OpenFlow Protocol to XAPI Plugin) (#9999)
* fix(configuration.md): remove trailing spaces

* feat(sdn-controller.md): explain traffic rules migration path: from OpenFlow Protocol to XAPI Plugin

- add sdn-migration.py python script
- in configuration.md, refer to sdn_controller.md for changing `useDirectChannel`
- in sdn_controller.md, provide documentation on migration path
2026-06-24 08:53:34 +02:00
Sebastien Marie
8f31ff7481 feat(xo-server-sdn-controller): traffic-rules: check the configuration matches the rules format (#10000)
XCPNG-3260

report an error if we detect that the runtime configuration doesn't match the of-rules format stored in xapi database.

the admin is expected to ran migration script by hand to fix it.
2026-06-24 08:52:48 +02:00
Enée DI IORIO
d7bfb171fd feat(lite): Use web-core's UiCardTitle instead of xo-lite version (#9982) 2026-06-23 17:10:25 +02:00
Sylvère
fc45f3e59e feat(web-stack): extract SR types/utils to web-core (#10004) 2026-06-23 17:09:22 +02:00
Florent BEAUCHAMP
cecf1cb6bc feat(xoa): add an option to take a snapshot before upgrade (#9920) 2026-06-23 16:10:29 +02:00
Thomas Moraine
bf0e40977c docs(sdn_controller.md): mention that network-wide rules can now be created in the UI with XO6 (#10019) 2026-06-23 15:33:40 +02:00
Marie Garde
caa174c69c feat(xo6): vdi dedicated page (#9949) 2026-06-23 11:37:52 +02:00
Mathieu
304e219441 fix(rest-api/dashboard): count job sequence schedules for backup computation (#9997) 2026-06-23 10:24:57 +02:00
Enée DI IORIO
9c2739b6dd feat(lite): Use web-core's UiCard instead of xo-lite version (#9980) 2026-06-23 10:16:31 +02:00
Pierre Brunet
7a24dba9c1 fix(backup/cleanVM): avoid useless warning (#9996) 2026-06-23 10:08:32 +02:00
Mathieu
a5d410e0aa feat(xo-server-openmetrics): include XO tags as tags label (#9971)
Fixes #9628
Plane : XO-2244
2026-06-23 10:07:15 +02:00
Thomas Moraine
64401c4040 docs: make a more logical doc structure for xo6 (#9972)
This commit creates a new, refreshed tree structure
for the XO 6 documentation, which we intend
to be more helpful for readers and XO 6 users.
New sections are created, to be completed later.
Additionnally, the ACLv2/RBAC page has been
moved from the Support section to the Security section.

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2026-06-23 09:41:13 +02:00
Pierre Briffaux
93909d9851 feat(xo6/rbac): add admin tab with user management menu (#9947) 2026-06-22 17:25:42 +02:00
Grandalf
5604289f3b test(sdn-controller): add tests (#9957)
Adds rest api endpoints testing for the sdn controller.
Don't forget to set the required SDN_CONTROLLER_VM_ID to a vm that has both a vif and a network.
Run @xen-orchestra/qa-test yarn qa:sdn-controller for testing.
2026-06-22 17:09:39 +02:00
Mathieu
cf569efff2 feat(rest-api): add PATCH /rest/v0/vifs/{id} (#9935) 2026-06-22 15:54:05 +02:00
Sandrine Barrucand
4bde431167 feat(xo6/traffic-rules): add delete button (#9956) 2026-06-18 12:14:56 +02:00
Mathieu
37868e3a74 fix(rest-api): fix vm dashboard replica detection (#9991)
introduced by 680b810886

See https://github.com/vatesfr/xen-orchestra/pull/9806/changes#diff-6f2227ada6bc2da3fd324691bb30b604b7c61a2cdbeebcad7969a8233cd4f0c4R394
2026-06-18 10:40:13 +02:00
Florent BEAUCHAMP
443de97c57 fix(qa-test): metadata test can run so fast there is a datetime collision (#9954) 2026-06-17 17:57:02 +02:00
vates-bot
9e39d0d534 feat(web-stack/i18n): update Xen Orchestra 6 (#9914) 2026-06-17 17:01:24 +02:00
Mathieu
f166037b4d docs(rest-api): update .USAGE.md (#9967)
* docs(rest-api): update .USAGE.md

* Mathieu.P's feedback
2026-06-17 15:19:00 +02:00
Alina D
60c97d3678 feat(xo6): add policy tag colors (#9979) 2026-06-17 15:08:53 +02:00
Joris K
62ba0a5a30 feat(web-core): update table header to remove icons (#9943) 2026-06-17 11:36:50 +02:00
Pierre Brunet
2a5c350a15 fix(disk-cli): update isDisk call parameters (#9988) 2026-06-17 10:55:34 +02:00
Pierre Brunet
4a8b29530b chore: fix changelog (#9993) 2026-06-17 10:15:02 +02:00
Pierre Brunet
9a710e5bf3 feat: update packages (#9969) 2026-06-17 10:02:00 +02:00
Sylvère
8eb6543905 feat(xo6): add the SR connect feature (#9877) 2026-06-16 11:52:29 +02:00
Alina D
b99f686d0c feat(xo6): update CPU labels to vCPU in VM system resource card (#9950) 2026-06-16 10:59:19 +02:00
Stephen Boyd
07b45e157d feat(xo-server-netbox): add option to ignore private IPs (#6306)
Optionally ignore RFC 1918 VM IPs during synchronization
2026-06-16 09:33:39 +02:00
Enée DI IORIO
3e7f003060 feat(lite/xoa-deploy): add confirmation before leaving deployment (#9893)
Add a message if you try to leave the page during XOA deployment.

Unfortunately, it is not possible to have a custom info box for the
`beforeUnload` event. So for homogeneity, `onBeforeRouteLeave` is
`window.confirm` too.
2026-06-16 09:27:00 +02:00
MlssFrncJrg
cf26d3f5b2 feat(rbac): create Administrator template role (#9885) 2026-06-15 15:50:09 +02:00
Florent BEAUCHAMP
60f59509a9 fix(backups): remove inspector (#9966) 2026-06-15 14:31:31 +02:00
Bastien Nollet
801a1f70fe feat(load-balancer): wrap migrations in XO Tasks for readability (#9909)
Use XO Tasks in the load balancer so users can see on the task page what the load balancer does. Now, it creates a task (with no progress) for each VM migration.

The task has a description explaining the reason of the migration, which can be accessed by opening the task log.

XO-992
2026-06-15 13:59:48 +02:00
Enée DI IORIO
b06779655e fix(web-core/VtsStateHero): behavior on mobile (#9363) 2026-06-15 13:46:08 +02:00
Grandalf
d1b99b9e86 feat(rest-api): add forgetRepository endpoint (#9846) 2026-06-15 10:59:09 +02:00
Mathieu
04deb9e328 feat(acl): add Network administrator built-in role template (#9952) 2026-06-12 15:47:22 +02:00
Nathanaël HANNEBERT
8c51de5043 fix(docs): typo preventing building of docs 2026-06-12 11:48:50 +02:00
Florent BEAUCHAMP
de5ea8a888 fix(backups): file level restore : omnibus fixes (#9776) 2026-06-12 11:40:16 +02:00
Marie Garde
e9b66fe607 feat(xo6): add column vif to table Vm > Network (#9959) 2026-06-11 17:03:46 +02:00
Sylvère
d5d9c00c70 feat(xo6): add the SR disconnect feature (#9856) 2026-06-11 16:04:53 +02:00
Joris K
ae2ef7a7d9 feat(xo6): update actions name to be more consistent (#9968) 2026-06-11 14:25:57 +02:00
Sylvère
899d3e27bb feat(xo6): add the SR delete feature (#9853) 2026-06-11 14:11:46 +02:00
Sylvère
632532f88a docs: update glossary (#9857)
Updated glossary with more acronyms and definitions.
2026-06-11 11:42:10 +02:00
Sylvère
cbf7da417f feat(web-core): updated UiModal to v3 (#9825) 2026-06-11 11:40:11 +02:00
Florent BEAUCHAMP
1f826f7d5d feat(qa-test): add minimal runner and mail report (#9866) 2026-06-11 11:07:37 +02:00
MlssFrncJrg
ae79d51d87 feat(rest-api): add RBAC check for VM endpoints (#9910)
* feat(rest-api): add RBAC check for VM endpoints
2026-06-11 10:33:12 +02:00
MlssFrncJrg
21f191b829 feat(rest-api): add RBAC check for VDIs endpoints (#9908)
* feat(rest-api): add RBAC check for VDIs endpoints
2026-06-11 10:27:00 +02:00
Elise-F
ba5b9ba7f9 fix(xo-lite): vue-router@5 breaking changes (#9951) 2026-06-11 09:49:39 +02:00
Alina D
88045806f0 feat(web-core/xo6): update connect/disconnect icons (#9942) 2026-06-11 09:02:12 +02:00
Pierre Brunet
1ba82a9304 fix(backups): remove warning for old vdis not removed yet (#9958)
* fix(backups): remove warning for old vdis not removed yet
2026-06-10 16:31:58 +02:00
Mathieu
d6d530d616 feat(xo6/lite): add ha_reboot_vm_on_internal_shutdown in pool's system (#9962) 2026-06-10 11:55:09 +02:00
Mathieu
c61083e59e fix(rest-api): vm export correctly handle zstd and gzip compression (#9960) 2026-06-10 10:55:13 +02:00
Pierre Donias
e376a0e102 feat(netbox): support Netbox v4.6.x (#9939)
Fixes #9818
See https://github.com/netbox-community/netbox/releases
2026-06-09 17:15:02 +02:00
Bastien Nollet
13e0cd6b79 fix(xo-server): guards against reading property of undefined (#9944) 2026-06-09 16:59:35 +02:00
Mathieu
3f9c8ecce0 fix(vates/types): remove duplicate entries (#9913)
Introduced by 544590a860
2026-06-09 14:04:10 +02:00
Pierre Brunet
7b40af7e86 feat(qcow2): fix invalid range error (#9948)
Reduce memory consumption up to 700MB / TB of data ( 300-350Mb per call of getBlockIndexes().length , called twice).

Ticket: 59102
Tested in client environment. Works for a 7TB import
2026-06-09 11:54:04 +02:00
Mathieu
d04421ed54 fix(xo-server): fix authorization unit test at startup (#9965) 2026-06-09 11:52:07 +02:00
Florent BEAUCHAMP
3500d6c6c1 fix(rest-api): properly handle unauthorized error (#9938) 2026-06-08 16:04:06 +02:00
MlssFrncJrg
442e63f716 feat(rest-api): add RBAC check for VDI-snapshots endpoints (#9906) 2026-06-08 15:14:22 +02:00
Grandalf
ed670fbe36 feat(ipmi-plugin): Add a default value for outlet temp to the Dell preset (#9884)
feat(ipmi-plugin): Add a default value for outlet temp to the Dell preset
2026-06-08 15:01:36 +02:00
Florent BEAUCHAMP
59780ef834 feat(qa-test): user url instead of fs (#9898)
this enable the test to run using from and to shared storage ( nfs/samba/s3/azure)

the local path is only used for export tests

tested with a remote garage (object storage) provider, full suite is passing
2026-06-08 14:55:59 +02:00
spacotte-vates
53be744746 feat(rest-api): add add_host route to pools (#9876) 2026-06-08 11:29:31 +02:00
Mathieu
7adcf9afd3 feat(xo-server): support bundle in featureCheck (#9769) 2026-06-08 10:16:03 +02:00
MlssFrncJrg
7f693de5a5 feat(rest-api): add RBAC check for PBD endpoints (#9888)
* feat(rest-api): add RBAC check for PBD endpoints
2026-06-08 09:51:46 +02:00
MlssFrncJrg
fc67f88fcb feat(rest-api): add RBAC check for SRs endpoints (#9896)
* feat(rest-api): add RBAC check for SRs endpoints
2026-06-08 09:44:21 +02:00
Pierre Donias
81b14af429 fix: filter syntax doc link (#9929) 2026-06-08 09:30:35 +02:00
Florent BEAUCHAMP
8939a2deeb feat(backups): add progress to incremental mirror (#9901) 2026-06-05 16:00:02 +02:00
Florent BEAUCHAMP
d8094a71e2 fix: normalize packages (#9917)
* fix: normalize packages

no changelog since it does not need any release

* put back lost chanegs
2026-06-05 14:12:57 +02:00
Florent BEAUCHAMP
c070df74ed test(backups): add e2e test for metadata and pool backups (#9864)
add end to end test for pool metadata and xo config backup
2026-06-05 14:08:40 +02:00
Sebastien Marie
2b9524e8d5 fix(xo-server-sdn-controller): apply/clean network rules on VIF update (#9933)
when a VIF is unplugged, network rules applied to the VIF aren't properly removed.
2026-06-05 14:07:38 +02:00
Pierre Briffaux
199651984b feat(xo-core): create drawer component (#9555) 2026-06-05 11:32:11 +02:00
Grandalf
aa7e1bb44b perf(sdn-controller): replace xapi.objects.all with specified object types (#9886) 2026-06-05 10:54:47 +02:00
spacotte-vates
ebdc6713c0 fix(qcow2): fix init map error with large Qcow2 disks (#9940) 2026-06-05 10:54:12 +02:00
Bastien Nollet
3ba207e1f0 fix(xo-web): fix array handling in job UI (#9928) 2026-06-04 16:06:55 +02:00
Grandalf
ebb337b786 feat(rest-api): add RBAC check for vif endpoints (#9889) 2026-06-04 15:36:33 +02:00
MlssFrncJrg
f4a7b708c1 feat(rest-api): add RBAC check for VBDs endpoints (#9904) 2026-06-04 15:19:27 +02:00
spacotte-vates
5a88040318 feat(rest-api): add POST and PATCH backup repository endpoints (#9852) 2026-06-04 15:10:20 +02:00
Enée DI IORIO
f0fe294628 feat(lite): Use web-core's UiTitle instead of xo-lite version (#9869) 2026-06-04 12:59:21 +02:00
Mathieu
e6443edb6e fix(qa-test): fix NodeJS version in README (#9930) 2026-06-02 09:32:40 +02:00
Pierre Brunet
daefcab0f2 chore: move latest tag (#9932) 2026-06-01 13:48:18 +02:00
Pierre Brunet
c39b9e8760 feat: release 6.5.1 (#9931) 2026-06-01 13:37:33 +02:00
Pierre Brunet
e822276d62 feat: technical release (#9927) 2026-06-01 11:31:40 +02:00
Pierre Brunet
c792d1e1dc chore: update packages to release (#9926) 2026-06-01 11:16:51 +02:00
Florent BEAUCHAMP
4552fb9edf fix(backups): disable orphan dir cleanup
this feature can lead to more cleanup than needed, we will enable
if again when all the side effects are handled, after an extented
real world usage
2026-06-01 10:56:35 +02:00
Florent BEAUCHAMP
ecceb7c0b8 fix(backups): backup cleanup removed to many files when listing fails 2026-06-01 10:56:35 +02:00
Mathieu
f875ed5264 docs(mcp): require Xen Orchestra 6.5 or later (#9922)
* docs(mcp): require Xen Orchestra 6.5 or later

* docs(mcp): match warning/note admonition style
2026-06-01 09:29:21 +02:00
Florent BEAUCHAMP
79b87855d9 fix(xo-server): fix xo xcp ssh path and help (#9919) 2026-05-29 16:40:40 +02:00
Florent BEAUCHAMP
014fcb94b3 fix(backups): transfer size (#9900)
this is an async bug leading to the full size not really sumed, thus
depending on the async and concurrency leading to a size 0 even if another
disk has data
2026-05-29 16:04:08 +02:00
laszlobortel
16bf4400ba fix: correct 2 typos (#9873)
* Update esxi.mjs

* Update importVdi.mjs

Correct typo

* Update importDisksfromDatastore.mjs

Correct typo

* Update CHANGELOG.unreleased.md

Updated list of changed packages
2026-05-29 15:57:51 +02:00
Pierre Brunet
dec0cf7943 fix: bump lite version (#9916) 2026-05-28 16:14:36 +02:00
Pierre Brunet
315686678a feat(lite): 0.21.0 (#9915) 2026-05-28 16:02:11 +02:00
Pierre Brunet
2b887158ba feat: release 6.5.0 (#9912) 2026-05-28 15:31:56 +02:00
Florent BEAUCHAMP
51546d475f docs(immutable-backups): fix and improve the documentation (#9890)
better reflect the new implementation of immutable backup settings and troubleshooting in the doc
2026-05-28 14:53:41 +02:00
Pierre Brunet
0f29421627 feat: technical release (#9907) 2026-05-28 11:11:08 +02:00
Grandalf
781c08016f feat(acls-v2): add VMS_ADMINISTRATOR template (#9845) 2026-05-28 10:54:58 +02:00
Grandalf
5c157c1367 feat(xo-server): add date and time to exported config filename (#9859) 2026-05-28 10:32:19 +02:00
Mathieu
5089be9998 feat(rest-api/vm): add PATCH /vms/{id} to partially update a VM (#9835) 2026-05-28 10:17:15 +02:00
vates-bot
5c555170f7 feat(web-stack/i18n): update Xen Orchestra 6 (#9780) 2026-05-28 08:53:35 +02:00
Mathieu
40e944c901 fix(docs): wrap {master, satellite} in backticks on xo5/advanced page (#9902) 2026-05-28 08:42:07 +02:00
Elise-F
75b61b84c5 feat(xo6): add general tab vif page (#9831) 2026-05-27 17:10:16 +02:00
Joris K
f0aa3df923 feat(xo6): add possibility to create a VDI (#9848) 2026-05-27 16:38:06 +02:00
Elise-F
00844c66d9 feat(xo6): creating vif page with traffic rules table and form to add new traffic rule from VIF (#9837) 2026-05-27 16:31:19 +02:00
Pierre Brunet
1f243bf90e feat: update http-server-plus (#9899) 2026-05-27 16:05:43 +02:00
Pierre Brunet
b3d9dc6d0c feat: technical release (#9897) 2026-05-27 15:16:53 +02:00
Florent BEAUCHAMP
405724eacb fix(qa-test): remove rebase issue and cleanup (#9894)
introduced by #9863
also use rest api for stop
2026-05-27 14:38:12 +02:00
spacotte-vates
20222f7051 fix(encryption): fix 401 issue caused by key deletion before set (#9892) 2026-05-27 14:37:33 +02:00
Florent BEAUCHAMP
dae64be80b fix(backups): omnibus and e2e test improvement (#9863)
* fix(backups): tolerate ENOENT when deleting missing backups

* fix(xo-server): propagate content-length in VM import

* fix(qa-test): retry REST API calls on transient 401

* fix(qa-test): harden cleanup against stale cache files

* fix(qa-test): fail fast on misconfigured test environment

Assert that source and mirror repositories are distinct before running
any backup steps, giving an actionable error when both env vars resolve
to the same remote ID. Also improve the mkdir failure message in the
export test to include the exact path and the remediation command.

* feat(qa-test): make source vm runs to ensure there is content to transfer

* fix: harden job deletion

* fix(qa-test) don't use a deprecated route for authent

* fixup! feat(qa-test): make source vm runs to ensure there is content to transfer

* fixup! fix(backups): tolerate ENOENT when deleting missing backups

* fixup! fix(backups): tolerate ENOENT when deleting missing backups
2026-05-27 09:27:12 +02:00
Mathieu
fc4d34b354 feat(openmetrics): expose XOSTOR metrics for Grafana dashboards (#9849)
Implements XO-2155. The OpenMetrics plugin now ships XOSTOR-specific metrics so operators can build a Grafana dashboard for XCP-ng Hyperconverged Infrastructure.

All metrics use the xcp_xostor_* prefix:

    Cluster status: xcp_xostor_up, xcp_xostor_node_status (master / satellite roles + raw state from linstor-manager.healthCheck), xcp_xostor_resource_total.
    Storage pool health: xcp_xostor_resource_state_count — replica counts per disk-state (UpToDate, Inconsistent, Outdated, Diskless, Unknown), aggregated per cluster.
    Performance filter: a new sr_type label on every SR-tagged metric (xcp_host_disk_iops_*, xcp_host_disk_throughput_*, latency, xcp_sr_*, xcp_vdi_*, xcp_vm_disk_*). Dashboards can filter sr_type="linstor" without changing existing queries.
    Active alarms: xcp_xostor_alarms_count aggregated by alarm_name (ALARM, BOND_STATUS_CHANGED, MULTIPATH_PERIODIC_ALERT) and target_type (sr / host).
    Disk SMART: xcp_xostor_disk_smart_status per (host, device) with the overall-health string in a status label, sourced from smartctl.py.
    Pending updates: xcp_xostor_package_update_available for xcp-ng-linstor, linstor-satellite, linstor-controller, xcp-ng-release-linstor, and xcp-ng-xapi-plugins.

Each collector caches at its own TTL (60 s for cluster and alarms, 5 min for SMART, 1 h for updates) and exposes an xcp_xostor_<feature>_up gauge so silent collection failures still show up on dashboards. A TtlCache<T> helper encapsulates the cache-plus-coalescing pattern shared by the three caching collectors.
2026-05-26 17:57:40 +02:00
Mathieu
5195018361 feat(mcp): whitelist REST endpoints by x-mcp-exposure extension (#9875)
Replace the hard-coded CONFIRM_PATTERNS list in the MCP server with a per-endpoint exposure decision carried by an x-mcp-exposure OpenAPI vendor extension. Each REST endpoint now declares its MCP exposure (allow / confirm / deny) with @Extension('x-mcp-exposure', ...) next to its HTTP method decorator. A new local ESLint rule mcp/require-mcp-expose makes the decision mandatory: any new @Get/@Post/@Put/@Patch/@Delete method without an exposure tag fails lint.

The MCP parseSwagger now reads the extension and applies a default-deny policy:

    'allow' → exposed as a query/action tool
    'confirm' → exposed only when XO_MCP_ENABLE_ACTIONS=1, with confirm-token round-trip
    'deny' / absent → never exposed

Defense in depth is preserved: route-filter regex (binaries, streams, stats) is applied before the exposure check, so a binary endpoint mistakenly tagged 'allow' still stays out of the MCP surface. The confirm_token UUID + TTL machinery in risk-config.mts is untouched.

All 248 endpoints across 34 controllers have been annotated with conservative defaults (GET → allow, destructive POST/DELETE/PUT/PATCH → confirm, binary exports → deny).
2026-05-26 17:50:09 +02:00
Joris K
a9f8561cdd feat(xo6): add possibility to revert a snapshot (#9862) 2026-05-26 16:38:05 +02:00
Elise-F
8d318e8eb2 feat(xo6): add possibility to add new traffic rule from pool (#9809) 2026-05-26 16:24:44 +02:00
Mathieu
406a4aa00a feat(mcp): add global kill-switch (#9860)
Adds a server-side kill-switch for MCP. Setting [mcp] enabled = false in xo-server's config blocks every MCP connection globally
2026-05-26 16:21:22 +02:00
Mathieu
9abb66b594 fix(rest-api): post vdi other_config optional (#9883)
* fix(rest-api): post vdi other_config optional

introduced by 0bf0a46
2026-05-26 14:48:36 +02:00
Florent BEAUCHAMP
b17f43d676 fix(xo-web): ignore the number of snapshot of replicated VM in the health dashboard (#9868)
the number of snapshots will be as high as the retention selected, but this is not really
an issue since those VM will be run only when a disaster occurs. In this case, degraded
performance is an accepted tradeoff
2026-05-26 10:55:49 +02:00
Florent BEAUCHAMP
59a04d71a6 fix(xo-server): really use @vates/http-request-plus (#9842)
introduced by c6ccd987e2
2026-05-26 10:38:09 +02:00
spacotte-vates
fdcc999f66 fix(backups): fix out_of_range -4 merge error (#9782)
Introduced by #9300

Ticket #57310

Fix OUT_OF_RANGE error occurring when resuming failed merge.
2026-05-26 09:56:54 +02:00
spacotte-vates
1d9a6ac17f fix(xo-server): fix wrong boot order when hvm template has vdis (#9867)
Introduced by 7a8ca

When creating an HVM VM from a template that already has a disk attached, the boot order was being incorrectly modified to put the network first, resulting in the VM attempting a PXE boot before eventually falling through to the disk.
The boot order was modified whenever vdis and existingVdis were both empty, since no new disk needs to be provisioned. This caused network to be put first in the boot order regardless of whether a network install was actually requested.

Boot order modification now depends exclusively on installMethod:

    Network: network (n) moved to the front of the string, this is unchanged.
    CD: DVD-drive (d) moved to the front of the string, this is a fix to another issue where HVM + CD installs did not update the boot order string at all.
    Everything else: String is unchanged, preserving the template's original order

Also, previously creating a diskless HVM VM with no install repository would get the network moved to the front of the boot order, that implicit behavior is gone, now diskless VMs now inherit the template's boot order.
This means that if in the template boot order, the network is last, the BIOS will try to find the disk and the cd first before trying the network. This is acceptable as these steps are fairly short.
2026-05-26 09:44:26 +02:00
Pierre Brunet
a467f12526 fix(backup): add warning if xva is missing and update tests (#9870) 2026-05-26 09:20:37 +02:00
Joris K
5811d12ed4 feat(xo6): add possibility to attach a VDI (#9772) 2026-05-22 17:51:40 +02:00
OlivierFL
94bc7a6e03 fix(xo6): route-map incorrectly formatted by Prettier on commit (#9881) 2026-05-22 17:37:45 +02:00
Mathieu
18556901d9 fix(rest-api): rename acl tag into rbac (#9874) 2026-05-22 17:23:38 +02:00
Pierre Brunet
598bb79edb fix: bump dependants to backup-archive (#9882) 2026-05-22 15:10:16 +02:00
Pierre Brunet
bb215d1ecc chore: revert backup-archive version (#9880) 2026-05-22 11:54:20 +02:00
Pierre Brunet
082ab9cf41 feat: technical release (#9878) 2026-05-22 10:34:38 +02:00
OlivierFL
e3a58ed351 chore(xo-core/xo-6): introduce form validation (#9630) 2026-05-20 14:47:32 +02:00
Mathieu
a3d8ab8dbb feat(rest-api): add RBAC check for host endpoints (#9840)
RBAC for 
  - `/hosts/:id/actions/disable`  
  - `/hosts/:id/actions/enable` 
  - `/hosts/:id/actions/management_reconfigure`
2026-05-20 13:12:00 +02:00
Florent BEAUCHAMP
680b810886 feat(backups): bi directional replication (#9806)
following the symmetrical backup PR

reuse any snapshot in common between the source and destination if 
* using incremental replication
* only one target
 * a common snapshot exists between source and destination , even from another job, based on the CONTENT_KEY

The same rule of reuse the existing VM target / create a new one of symmetrical backups applies

if the destination don't have any data between the snapshot and the active disk 
    update an existing VM 
else create a new VM but chain disks
2026-05-20 12:33:34 +02:00
spacotte-vates
a0a302430d fix(encryption): fix orphaned entry issue (#9854)
When a Redis entry has an ID but no corresponding data key, undefined was returned which caused errors when new data was added.

Error case was the configuration of a plugin failing to save.
2026-05-20 11:16:47 +02:00
Grandalf
7c9c7efb77 feat(xo-server-ipmi-sensors) : Migrated the old ipmi feature to a new plugin and added user defined sensors (#9724)
Move ipmi sensor discovery to it's own plugin and removed old logic, ships with a default configuration with the same behaviour as the old system.
The plugin can be configured via the UI, accepts an exact match for the vendor and regexes for the ipmi matching.

Tested via unit tests.

Tested with R620-L1 host on xo-lab by adding and removing Dell from configured vendors
2026-05-20 10:58:53 +02:00
Pierre Brunet
60fbce0df9 fix(backup-archive): resolve xva path relative to backup directory (#9865)
Path needs to be relative, not absolute in metadata

introduced by #9765
2026-05-20 10:48:43 +02:00
Florent BEAUCHAMP
5e717b6e04 fix(qa-text): better logs, better env , less magic (#9833)
- Add `getRequiredEnv()`: replaces all `process.env.X || 'default'` fallbacks
  with a hard throw so a missing or typo'd .env key fails immediately with a
  diagnostic message instead of silently operating on wrong defaults
- Add `logSetup.js`: dual-transport logging — console filtered by $DEBUG,
  plus a full debug-level file in /tmp for post-mortem inspection
- Add `assertRepositoryMatchesConfig()`: fails fast when a named repository
  in XO points at a different path than the one declared in .env
- Make `getAllowedCleanupPaths()` lazy so env vars are read after .env is
  loaded, not at ESM module evaluation time
- Pass full error objects (not `.message`) to all structured log calls
- Add retry logic on flaky operations and surface more detail on failures
- Fix: only purge non-empty backup repositories
- Fix: backup jobs no longer skipped under some conditions
- Fix: missing `backuprepository.delete` function
- Add `.USAGE.md` and update `README.md`
2026-05-19 16:12:33 +02:00
Florent BEAUCHAMP
ed558e9854 fix(xo-server): ssh cli tool script name (#9861) 2026-05-19 15:49:37 +02:00
Florent BEAUCHAMP
8a51c621da feat(xo-server): ssh tool to connect to hosts (#9844)
add xo-server-ssh executable that ease a connection to the hypervisors

Without parameters it list the pool uuid/master ip
With a pool uuid parameter it opens a sshg connection to the pool master 
With a pool uuid and ip, it will reuse the pool credentials to connect to the slave host
2026-05-19 15:41:37 +02:00
Enée DI IORIO
f464803053 feat(lite): create robots.txt (#9858) 2026-05-19 14:12:05 +02:00
Mathieu
09c352244e feat(mcp): support HTTP_PROXY, HTTPS_PROXY, NO_PROXY env vars (#9820)
* feat(mcp): support HTTP_PROXY, HTTPS_PROXY, NO_PROXY env vars

* chore(changelog): mention MCP proxy env var support

* refactor(mcp): delegate proxy env handling to undici
2026-05-19 12:06:21 +02:00
Mathieu
ec365231dd fix(@vates/types): explicit XoUser permission type (#9829) 2026-05-19 11:51:13 +02:00
Laudin Alessandro Molina Troconis
e0ae3db341 fix(docs): formatting and minor grammar errors in CONTRIBUTING.md (#9827) 2026-05-19 11:45:01 +02:00
spacotte-vates
16d1c7ce9d feat(rest): add revert snapshot route (#9788) 2026-05-19 10:53:05 +02:00
MlssFrncJrg
64422066c2 feat(@xen-orchestra/rest-api): add delete SR (#9464) 2026-05-18 17:06:57 +02:00
Pierre Brunet
abbdfe6cb7 fix(xo-web): update sanitize-html (#9851)
dependabot alert : https://github.com/vatesfr/xen-orchestra/security/dependabot/484
2026-05-18 16:19:13 +02:00
Joris K
4e1c8b7188 fix(xo6/tasks): issue with task type and size when accessing backup runs (#9841) 2026-05-18 16:06:36 +02:00
Pierre Brunet
100f11308a refactor(backups): rewrite and test cleanVm (#9765)
the cleanVm script was a monolith in charge of cleaning the backup, 
detecting any corrupted data, deleting the irrecoverable one, removing
and merging older backups 

this PR do a complete rewrite, using the RemoteDisk abstraction and add
tests
2026-05-18 15:15:56 +02:00
spacotte-vates
d810ea0c46 feat(rest-api): refactor registerRestApi (#9553) 2026-05-13 17:53:15 +02:00
Mathieu
2b08359bcf fix(xo-server): fix read-only role by adding acl resources (#9839) 2026-05-13 15:00:21 +02:00
Thomas Moraine
0ede983c61 docs: add TOTP setup instructions to user documentation (#9843)
Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2026-05-13 14:56:27 +02:00
OlivierFL
8c00ccefd4 chore(xo-stack): update Vite, Typescript and Vue related libs (#9703) 2026-05-13 09:49:29 +02:00
spacotte-vates
6f68b04f7e feat(xo-server): encrypt/decrypt credentials in redis (#9735)
Implements encryption and decryption feature for Redis content with migration.
2026-05-12 16:29:34 +02:00
Pierre Brunet
ee53cd0723 fix(xo5/new-job): fix cpuMask options while using vm.set (#9823) 2026-05-12 15:25:10 +02:00
Grandalf
5985ac63f2 docs: added documentation for traffic rule REST endpoints (#9816) 2026-05-11 16:27:05 +02:00
Pierre Brunet
c89c184073 feat(fs): add custom user-agent in s3 client (#9815)
clientConfig:
  apiVersion: '2006-03-01',
  customUserAgent: [ [ 'xen-orchestra-fs-4.8.0' ] ],
2026-05-11 16:02:25 +02:00
Pierre Donias
407432b4e6 feat(xo-web/config): hide passphrase by default when exporting config (#9824)
* feat(xo-web/config): hide passphrase by default when exporting config

* PR
2026-05-11 11:05:35 +02:00
Pierre Donias
acdfcb4235 fix(xo-server/config): typo in comment of ignoreVmSnapshotResources field (#9822) 2026-05-11 11:03:41 +02:00
Enée DI IORIO
070a3da7e2 feat(web-core): Update UiChip component to V8 (#9491) 2026-05-11 10:47:35 +02:00
Enée DI IORIO
7a69b2c09a feat(web-core): Update UiTag to VtsTag and add tertiary (#9811) 2026-05-11 10:20:08 +02:00
Florent BEAUCHAMP
40eab4f4e7 feat(xo-web/xo-server): import qcow2 disk from form (#9817) 2026-05-11 08:19:01 +02:00
Julien Fontanet
c5b7e7d2cb fix(complex-matcher): avoid array allocation in isRawString (#9813) 2026-05-07 17:02:25 +02:00
Thomas Moraine
fd9e271139 docs(backup_reports.md) document the transport-nagios plugin (#9819)
The transport-nagios plugin lets users monitors backup jobs from the Nagios interface.
This was previously documented only in a Xen Orchestra blog post.
This commit completes the existing Backup reports page, by explaining how
to get backup reports in Nagios, using that plugin.

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2026-05-07 16:57:47 +02:00
Grandalf
008ec6ed6a feat(rest-api): add support for txChecksumming and rateLimitting on V… (#9793) 2026-05-06 19:16:22 +02:00
Florent BEAUCHAMP
2791a8c42d feat(qa-test): console.log=>xen-orchestra/log (#9810) 2026-05-06 16:50:32 +02:00
Enée DI IORIO
d4f1798530 feat(web-core/UiInfo): upgrade UiInfo to v6 (#9773) 2026-05-06 14:43:09 +02:00
Enée DI IORIO
7ef3dd3bbe feat(web-core/UiLink): upgrade UiLink to v6 (#9784) 2026-05-06 14:21:31 +02:00
Bastien Nollet
7e144234b9 feat: release 6.4.1 (#9808) 2026-05-05 12:30:45 +02:00
Bastien Nollet
b4f0e5f64d feat: technical release (#9807) 2026-05-05 10:42:40 +02:00
Mathieu
93e38c0280 fix(rest-api): early return for admin user in acl check (#9798)
* fix(rest-api): early return for non admin user in acl check
2026-05-05 10:09:10 +02:00
Sebastien Marie
93aba2dcc7 xo-server-sdn-controller: small cleanup (#9794)
This PR contains several commits to do some code cleanup in xo-server-sdn-controller.
They doesn't belong to any specific tasks, but it was done while working in the code.
They will be better upstreamed than in my local worktree.

    unify createLogger inside xo-server-sdn-controller package : it simplifies filter usage in configuration to have debug output of the sdn-controller
    in openflow-plugin.js, avoid using log.error() for a not fatal error but use log.warn(). reorder a bit the throw code to make it more readable (the error is throw only if no plugin call successed)
2026-05-05 10:04:39 +02:00
Florent BEAUCHAMP
80ba88bb0e fix(immutable-backups): use date in filename nstead of modified time to lock/unlock + fixes
Fixes: 
* better support of localized systems
* types
* add a retry on chattr error, selinux and / or xfs seems to need a little more time before locking it
* force a complete check on startup to remove immutables files that should not be froma previous run 
* don't try to lock deleted files
2026-05-05 10:00:16 +02:00
OlivierFL
ef1fe13667 feat(xo-stack): update system pages layout (#9746) 2026-05-05 09:26:19 +02:00
Thomas Moraine
225d1699e8 docs(sdn_controller.md): openSSL 3 + sdn upgrade path: fix error in code sample (#9805)
docs(sdn_controller.md): fix error in code sample

As reported by Jon Sands, the previous version of the code sample showed the script executing line by line. It works as expected after removing the -xe

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2026-05-05 08:34:21 +02:00
Thomas Moraine
0338013220 docs(v2v migration guide): Highlight link to V2V troubleshooting doc (#9797) 2026-04-30 18:28:05 +02:00
Thomas Moraine
893b229265 docs(aclv2): fix broken links to and from the new ACLv2 page (#9796)
docs(aclv2): fix broken links

The ACLv2 page was created and merged at roughly the same time we split the documentation pages into two sections (XO6 and XO5), so the change in file structure broke some links to and from that ACLv2 page.

This commit fixes the links so that the Docusaurus build can complete without errors.

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2026-04-30 10:34:47 +02:00
Thomas Moraine
598c71ca3c docs(xo): create a dedicated xo6 doc section (#9730)
XO6 has become the default interface for Xen Orchestra,
but not all features have been ported to XO5 yet
and they don't work the same way.
To make the documentation easier to maintain
and prevent confusion,
we're moving the current pages to a new section called 'XO 5'
and create a new section for XO6-specific documentation. The XO 6 section will be completed gradually.
On top of that, this commit updates internal links and redirections
to adapt to this new doc architecture.
Finally, most of the pages in the Support category
have been moved to a new section called General support,
since these pages apply the same way to XO 5 and XO 6.

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2026-04-30 09:48:45 +02:00
Mathieu
a04c5d671f feat(acl-v2): ACL v2 documentation (#9795)
#9754 was merged into the wrong branch
2026-04-30 09:34:21 +02:00
Bastien Nollet
13f987ff3d feat(lite): 0.21.0 (#9792) 2026-04-29 17:41:36 +02:00
Bastien Nollet
ad85a881dd feat: release 6.4.0 (#9791) 2026-04-29 16:54:08 +02:00
Grandalf
b0c4b7c4cc chore(docs): update installation command to include libfuse2 (#9751) 2026-04-29 16:26:19 +02:00
Bastien Nollet
2f1155af78 chore(changelog): forgotten package (#9790) 2026-04-29 16:24:49 +02:00
Bastien Nollet
81f83d6f22 feat: technical release (#9789)
* feat(@xen-orchestra/web): 0.49.0

* feat(xo-server-sdn-controller): 1.3.1

* chore(CHANGELOG): update next
2026-04-29 14:50:23 +02:00
Pierre Briffaux
c388d998d8 fix(vm/console): fix VNC reconnection causing focus loss (#9727) 2026-04-29 14:21:41 +02:00
Elise-F
b6a303e02a fix(xo6): remove dev routes from prod (#9787)
Introduced by: #9747
2026-04-29 14:07:53 +02:00
Joris K
a1f660d31b feat(xo6): add possibility to connect or disconnect a VBD (#9726) 2026-04-29 13:54:16 +02:00
Pierre Briffaux
c632fd2032 feat(xo6/vm/new): removed new VM form alert (#9757) 2026-04-29 13:46:45 +02:00
spacotte-vates
cfb91c8a1c fix(sdn-controller): fix invalidParameters import (#9786)
Introduced by 1f0a3d8
2026-04-29 11:30:22 +02:00
Bastien Nollet
33b9106da9 feat: technical release (#9785)
* feat(@vates/types): 1.24.0

* feat(@xen-orchestra/acl): 1.0.0

* feat(@xen-orchestra/backups): 0.72.1

* feat(@xen-orchestra/web-core): 0.51.0

* feat(@xen-orchestra/mixins): 0.20.0

* feat(@xen-orchestra/proxy): 0.30.1

* feat(@xen-orchestra/rest-api): 0.30.0

* feat(@xen-orchestra/web): 0.48.0

* feat(xo-server): 5.200.0

* feat(xo-server-sdn-controller): 1.3.0

* chore(CHANGELOG): update next
2026-04-29 11:00:24 +02:00
Florent BEAUCHAMP
12fb701686 fix(backups): handle diskless vm snapshot retention (#9758)
* fix(backups): handle diskless vm snapshot retention
2026-04-29 10:25:23 +02:00
Joris K
b83a7f9758 feat(xo6): add possibility to connect or disconnect a VIF (#9756) 2026-04-29 10:20:42 +02:00
Grandalf
95b9d28e6a feat : Make "allow = false " accepted in the traffic rules Rest API (#9783) 2026-04-29 09:42:29 +02:00
Bastien Nollet
3d1df14373 fix(backups): missing logs (#9781)
Fix some types of backup and restore logs not being returned by getBackupNgLogs
Make log garbage collector treat restore logs as backup logs
2026-04-29 09:21:52 +02:00
Elise-F
0eb5c2fb65 feat(xo6): implement security tab on pool to display traffic rules (#9770) 2026-04-29 09:13:00 +02:00
Florent BEAUCHAMP
c998f99e9d fix(proxy): improve error to better differentiate between source error and import error (#9775) 2026-04-28 16:49:59 +02:00
MlssFrncJrg
ec1ebd1687 feat(xo-server): add DNS-01 challenge support (#9592) 2026-04-28 16:40:12 +02:00
Grandalf
c868b788fb feat(rest-api): add and remove vif rules routes (#9759) 2026-04-28 15:39:56 +02:00
Florent BEAUCHAMP
144d95aac7 fix(backups): failed cleanVm test (#9778)
- ancestor (blocks 1, 3) merged into child (blocks 1, 2) -> 3 blocks
 - cleanAncestor merged into clean -> 0 blocks
 grandchild is not merged so its size is not counted here
2026-04-28 14:40:41 +02:00
OlivierFL
781e8a357f feat(xo-6/network): implement network deletion (#9714) 2026-04-28 11:59:24 +02:00
Julien Porschen
a4382b7687 fix(xo-server): display of RHEL 10 icons in vm list (#9766)
os_version.distro changed from "rhel" to "red hat enterprise linux" since version 10
2026-04-28 11:32:55 +02:00
jhoustek
7378bde4a8 fix(backups): record actual incremental VHD size (#9637)
* fix(backups): record actual incremental VHD size
* fix(backups): record merged VHD size from merge result
* fix(backups): only rewrite metadata after actual merge
* fix(backups): record actual incremental VHD size
2026-04-28 11:28:12 +02:00
spacotte-vates
1f0a3d8115 feat(rest-api): add and remove network rules routes (#9448) 2026-04-28 11:27:22 +02:00
vates-bot
7f1b8949cb feat(web-stack/i18n): update Xen Orchestra 6 (#9729) 2026-04-28 11:23:59 +02:00
Mathieu
71b2e373ab feat(mcp) : openapi bootstrap (#9641)
* refactor(mcp): split monolithic index.mts into modular file structure

* feat(mcp): add markdown formatters for LLM-optimized responses

* feat(mcp): dynamic tool generation from OpenAPI spec at startup

* fix(mcp): remove orphan tests and fix mock client after rebase

* refactor(mcp): clean up dead code, consolidate formatters, use
human-format

* docs(mcp): reflect dynamic tool generation + actions flag

* refactor(mcp): generate tools directly from OpenAPI spec

* refactor(mcp): drop redundant tool annotation hints
2026-04-28 11:12:58 +02:00
Mathieu
63d4334837 feat(rest-api): ACL V2 integration (#9774) 2026-04-28 11:03:49 +02:00
Sebastien Marie
00560dd418 feat(xo-server-sdn-controller): add the openflow controller method used to pool:other-config
it will be used by xapi to know if the OpenFlow channel is used and so if port 6653 needs to be opened or not.
it might also be used later to know how to interprete of-rules in the migration path from channel to xapi-plugin, if needed.

XCPNG-2470
XO-2282
2026-04-28 09:42:57 +02:00
Sebastien Marie
d56b68d49a fix(xo-server-sdn-controller): allow pool.certificate_uninstall to fail with CERTIFICATE_DOES_NOT_EXIST
It could happens if xapi database is out-of-sync with filesystem.

As the purpose of this block is to decide if the certificate should be installed,
assume that on CERTIFICATE_DOES_NOT_EXIST you need to install the new certificate.
2026-04-28 09:42:03 +02:00
Sebastien Marie
954a611399 fix(xo-server-sdn-controller): generate psk password using cryptographically secure pseudorandom number generator
For strong passwords, you need a good source of entropy (true randomness).  
lodash.sample's randomness relies on the JavaScript runtime's PRNG, which might 
not have enough entropy.
2026-04-28 09:42:03 +02:00
Sebastien Marie
c5c3cc23a2 fix(xo-server-sdn-controller): fix VM events to propagate Network Rules on changes
on topology changes (VM started/stopped/migrated/...), network rules needs to be 
refreshed as they are installed on each port of virtual switch.

add the same logic than VIF rules to refresh them.
2026-04-28 09:42:03 +02:00
Sebastien Marie
657c639de2 fix(xo-server-sdn-controller): fix function name in error message (copy/paste error) 2026-04-28 09:42:03 +02:00
Sebastien Marie
b57765231f fix(xo-server-sdn-controller): manage network rules deletion with cookie
Enable the usage of `cookie` parameter exposed by sdn-controller xapi plugin
- https://github.com/xcp-ng/xcp-ng-xapi-plugins/pull/58

It fixes several cases where OF rules where leaked and left behind.

Upgrade notes (apply to Network Rules only, VIF Rules are not impacted):
- a new xapi-plugin with old XO is compatible (and no behaviour change)
- an old xapi-plugin with new XO will fail to communicate
  XO will try to use `cookie` but xapi-plugin will complain about invalid parameter
- a new xapi-plugin with new XO will use new behaviour
  and existing OF rules will migrate smoothly from old to new behaviour
2026-04-28 09:42:03 +02:00
Pierre Briffaux
20dfa98575 feat(xo6/host): show management IP first in hosts table IP column (#9747) 2026-04-27 16:38:42 +02:00
Elise-F
3bb0850626 fix(xo6): remove dev pages in prod (#9700) 2026-04-27 16:10:54 +02:00
Joris K
699fb196fb feat(xo6): add possibility to remove a snapshot (#9749) 2026-04-27 10:25:14 +02:00
Mathieu
00e0bee372 fix(xo-server): fix default-template still in xo after deletion (#9760)
When VM_destroy is called on a default template, it is first converted to a simple template (and then to a VM).

After converting a default template to a simple template, the object received after the xapiObjectToXo transformation has a different ID (the default template's ID = pool.uuid + template.uuid, and non default template ID = template.uuid).

Since this object does not already exist in the XO server cache(because the ID is new), XO treats it as a new object and not as an update, so the old object with ID pool.uuid + template.uuid stay in the cache
2026-04-27 08:39:51 +02:00
Florent BEAUCHAMP
da045e0de1 fix(backups): use a delta transfer even if the VM is modified (#9709)
* fix(backups): use a delta transfer even if the VM is modified
* test(backup): fix date test without timezone
* refactor(backups): extract method that check which snapshot can be reused for replica target
* reset before launching the replication
2026-04-24 19:30:51 +02:00
Bastien Nollet
fdb82ed68a feat: technical release (#9768) 2026-04-24 14:38:15 +02:00
Pierre Brunet
112c22e748 fix: add chainPhysicalUsage property in vdi object (#9708)
*add  chainPhysicalUsage property to vdi
2026-04-23 16:29:55 +02:00
Thomas Moraine
c540a49643 doc(users.md): recommend against using local users in prod environments (#9761) 2026-04-23 15:13:14 +02:00
Joris K
f6484a0695 feat(xo6): add possibility to delete a VBD (#9698) 2026-04-21 16:34:13 +02:00
Joris K
5593f27110 feat(xo6): add possibility to remove VDIs (#9689) 2026-04-21 15:41:15 +02:00
Joris K
81813c7e5b feat(xo6): add possibility to remove VIFs (#9601) 2026-04-21 15:19:06 +02:00
Mathieu
0bf0a468b5 fix(rest-api): fix other_config being ignored on VDI creation (#9695) 2026-04-21 12:35:33 +02:00
Pierre Brunet
e78275f046 docs: fix typo (#9739) 2026-04-21 11:39:49 +02:00
spacotte-vates
89c7551293 feat(rest-api): add connect/disconnect vif actions (#9643) 2026-04-21 10:25:24 +02:00
Mathieu
ed30291993 fix(rest-api): fix double slash in the href property (#9741)
Introduced by dd5f0cdaa9
2026-04-21 10:07:52 +02:00
Pierre Donias
9765435471 feat(xo-web/servers): remind users to only add pool masters (#9742) 2026-04-20 15:43:02 +02:00
spacotte-vates
e2a4bd2231 fix(changelog): add version 6.2.3 (#9743) 2026-04-20 15:10:02 +02:00
Florent BEAUCHAMP
c6ccd987e2 fix(xo-server): TLSSocket memory leak — stoppable + http-server-plus incompatibility (#9725)
xo-server uses two libraries for its web server:

    http-server-plus — a multi-protocol server facade that multiplexes HTTP and HTTPS listeners behind a single EventEmitter interface ( introduced here JsCommunity/http-server-plus@ec63f3f )
    stoppable — wraps the server to track open connections for graceful shutdown, using an internal reqsPerSocket Map (server._pendingSockets) (introduced here : 782505b )


Fix

@vates/http-server-plus (fork of JsCommunity/http-server-plus): add 'secureConnection' to the forwardedEvents list so that webServer.on('secureConnection', handler) is propagated to underlying https.Server instances at listen() time.

xo-server/src/index.mjs: register the missing handler that stoppable was supposed to add but never did:
2026-04-20 14:38:37 +02:00
Mathieu
45f810c044 feat(openmetrics): add VM status and uptime metrics (#9684)
* feat(openmetrics): add VM status and uptime metrics

* refactor(openmetrics): address review feedback on VM status/uptime
2026-04-20 14:09:28 +02:00
Bastien Nollet
fbba05bbde feat(backups): use XO Tasks (#9734)
Change the backups so they stop using their own task system, and use the same system as XO Tasks.

For the moment, it shouldn't affect the users, but when the previous backup logs will be old enough to be erased, this will allow us to have shorter loading times for backup logs.

XO-52
2026-04-17 17:49:18 +02:00
OlivierFL
9dc52f2e5b fix(xo-6/network-form): toRef causes TS error on build (#9736) 2026-04-17 17:33:06 +02:00
Mathieu
271d123395 fix(rest-api): SSE MiB limit instead percent (#9731) 2026-04-17 16:58:26 +02:00
Enée DI IORIO
88a89cc3a9 fix(changelog): fix PR link formatting in changelogs (#9732) 2026-04-17 11:46:47 +02:00
Florent BEAUCHAMP
5f7fab9d34 test(backups): replication and full mirror backups
* test(replication): test the new snapshot based replication

* fix(tests): use mergeBackupsSynchronously to ensure the backups don't fails with lock already heled error

* fix: ensure the repository that will be purged start empty

* test(backup): add test for full mirror backups
2026-04-17 11:25:38 +02:00
spacotte-vates
8df8ef0af8 feat(backups): log merge size (#9679)
XO-2050

Add the merged size in the cleanVm task log
2026-04-17 09:58:12 +02:00
Mathieu
6fbb961e7f fix(xo-server): fix 408 timeout when copying a VM with compression (#9699)
* fix compression not used on front whe copying a VM
* ensure the first bytes are ready before starting to import the VM 

---------

Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@vates.tech>
2026-04-17 09:56:39 +02:00
Mathieu
6b1a2afc9c docs(@vates/types): document vm.videoram (#9694) 2026-04-16 16:05:28 +02:00
Pierre Donias
3ed8c7b689 feat(netbox): introduce sub-platforms with major version number (#9644)
Fixes #7773
See Zammad#53857

On Netbox >= 4.4 (which introduced platform hierarchies):
- If the major version number is available: create a versioned child platform (e.g. "Debian 12") with the distro-only platform (e.g. "Debian") as its parent, and assign the child to the VM
- Otherwise: keep previous behaviour and assign the VM to the distro-only platform
2026-04-16 10:31:32 +02:00
Mathieu
db9c4815b1 feat(openmetrics): add 9 missing host RRD metrics (#9696) 2026-04-15 19:27:03 +02:00
Mathieu
3e21edad4e chore: upgrade dependencies with known security vulnerabilities (#9640)
* chore: upgrade dependencies with known security vulnerabilities

* Update Node.js version in CI workflow

* chore: upgrade additional security dependencies

* chore: stabilize yarn.lock path-to-regexp entry

* chore: bump minimum version ranges for security-patched dependencies

* chore: update CHANGELOG.unreleased.md with security upgrades

* chore: revert vite upgrade to 6.4.1 (6.4.2 breaks vue-tsc)

* chore: fix vite upgrade and pin vue to 3.5.28
2026-04-15 16:56:43 +02:00
vates-bot
1b12aebb47 feat(web-stack/i18n): update Xen Orchestra 6 (#9649) 2026-04-15 14:43:26 +02:00
p-bo
2e009535aa fix(i18n): shutdown -> reboot in force reboot dialog (as in French) (#9683)
99d19012ad/%40xen-orchestra/web-core/lib/locales/fr.json (L485)
2026-04-15 14:36:13 +02:00
Elise-F
5af32683ce feat(web-core): update default tab functionality (#9651) 2026-04-15 12:40:20 +02:00
Mathieu
0fef765ce6 fix(rest-api): fix memory-leak on subscription (#9707) 2026-04-15 12:18:06 +02:00
Pierre Donias
4508e01644 fix(CHANGELOG): markdown, URLs, labels, ... (#9723) 2026-04-15 11:56:25 +02:00
Pierre Briffaux
f60203a927 fix(web): fix lost focus on vdi name input (#9685) 2026-04-15 11:29:11 +02:00
Mathieu
5ebefb0af2 feat(openmetrics): add VDI disk size and physical usage metrics (#9680)
Fixes XO-1995
2026-04-15 09:52:26 +02:00
Thomas Moraine
fa8844037f docs(xen-orchestra): upgrade docusaurus to v3.10.0 (#9682) 2026-04-15 09:40:19 +02:00
Bastien Nollet
2098c02e7f chore(xo-web): remove unused function (#9691) 2026-04-15 09:37:11 +02:00
Mathieu
c4763d5eec feat(rest-api,mcp): add ?markdown=true output format to REST API (#9624) 2026-04-15 09:30:20 +02:00
Elise-F
0b78cab0b3 fix(web-core): match npm package version (#9721)
web-core 0.49.0 was accidentally published to npm.
This commit bumps the version to match what is already on npm.
2026-04-14 18:18:38 +02:00
Elise-F
bbf3e1e446 feat: release 6.3.3 (#9720) 2026-04-14 17:19:55 +02:00
Elise-F
d94de6090b feat: technical release (#9716) 2026-04-14 15:31:22 +02:00
Bastien Nollet
816b60f11b docs(backups): extra details (#9693) 2026-04-14 14:20:07 +02:00
Florent BEAUCHAMP
6b36b41d68 fix(xo-web): toggle distributed replication (#9715)
introduced by #9433
2026-04-14 11:44:26 +02:00
spacotte-vates
e9caeafd7a fix(bakups): fix merge resume issue when child is disk chain (#9668)
* fix(bakups): fix merge resume issue when child is disk chain
2026-04-14 11:04:23 +02:00
Pierre Brunet
06c3933756 test(backups): add unit test to check #9667 (#9670) 2026-04-13 18:30:17 +02:00
Mathieu
30ff04d88a docs(mcp): add multi-instance configuration (#9705) 2026-04-13 18:25:27 +02:00
Florent BEAUCHAMP
43d86038b1 fix(backups): Fix "Storage_error ([S(Illegal_transition);[[S(Activated);S(RO)];[S(Activated);S(RW)]]])" (#9702)
* don't connect NBD server if we only want the block list
* wait for the disk to be disconnected from dom0 before trying to write into it
2026-04-13 16:46:23 +02:00
Thomas Moraine
415207d1ba docs(intro_backup.md): fix broken youtube embed (#9701) 2026-04-13 10:41:57 +02:00
spacotte-vates
b21e5e8c53 fix(backups): fix vhd directory merge speed regression (#9692)
* fix(backups): fix vhd directory merge speed regression

* add changelog
2026-04-13 09:35:40 +02:00
OlivierFL
aa605639e4 feat(xo-6): network creation forms (#9629) 2026-04-10 17:35:21 +02:00
Elise-F
8ecaa6d817 fix(xo6): increase SSE ping timeout (#9681)
Introduced by #9375
2026-04-09 10:14:54 +02:00
Mathieu
76655a9e05 fix(@xen-orchestra/immutable-backups): fix missing prepublishOnly entry (#9687)
See zammad#51952
2026-04-08 16:12:20 +02:00
Elise-F
58b56a0749 fix(XO6): 'please wait' page not displayed on dev pages (#9686) 2026-04-08 14:41:50 +02:00
Thomas Moraine
99d19012ad docs(xo): replace some XO5 screenshots with the XO6 versions (#9678) 2026-04-07 09:24:38 +02:00
Elise-F
c1e5f2e8d7 feat: release XO 6.3.2 (#9676) 2026-04-03 15:21:41 +02:00
Elise-F
372a2be21d feat: technical release (#9673) 2026-04-03 10:47:59 +02:00
Florent BEAUCHAMP
3acd35142c fix(backups): don't fail incremental replication when the VM has VTPM (#9671) 2026-04-03 10:06:44 +02:00
Florent BEAUCHAMP
28c5e5f138 fix(backups): schedule reference is missing (#9667)
introduced by #9635
2026-04-02 14:47:00 +02:00
Elise-F
a6c508be46 feat(lite): 0.20.0 (#9666) 2026-04-01 16:27:01 +02:00
Elise-F
875ceb7cab feat: release XO 6.3.1 (#9665) 2026-04-01 15:08:02 +02:00
Thomas Moraine
d98dd78168 docs(manage-infrastructure.md): fix formatting errors in info panels (#9664)
Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2026-04-01 13:07:04 +02:00
Elise-F
df0c69c023 feat: technical release (#9663) 2026-04-01 10:37:53 +02:00
Mathieu
df7ffbb90c fix(web-core): fix objects store not updated (#9660)
* fix(web-core): fix objects store not updated
2026-04-01 10:10:23 +02:00
Mathieu
8553f1cd08 fix(rest-api): fix no such VDI for /vms/:id/(dashboard|vdis) (#9662)
* fix(rest-api): fix no such VDI for /vms/:id/(dashboard|vdis)
2026-04-01 10:07:10 +02:00
Florent BEAUCHAMP
41de7528bf fix(backups): full when going from 6.2 to 6.3 (#9661) 2026-04-01 09:48:55 +02:00
Elise-F
bfb0a052ef feat: release XO 6.3.0 (#9658) 2026-03-31 16:37:31 +02:00
Elise-F
5bd04b0736 feat: technical release (#9656) 2026-03-31 14:52:50 +02:00
Mathieu
4015987b03 fix(web-core): fix performance issue when lot of new events (#9627) 2026-03-31 12:05:00 +02:00
Elise-F
25db113349 feat: technical release (#9654) 2026-03-31 10:00:56 +02:00
spacotte-vates
b8637097c2 fix(backups): fix double close on merge (#9636)
* fix(backups): fix double close on merge

* set dispose to promise instead of empty func
2026-03-31 09:20:06 +02:00
Mathieu
9226c51df7 fix(rest-api): cannot read properties of undefined for vms/:id/dashboard (#9650)
Fix forum#12031
2026-03-30 20:07:45 +02:00
Joris K
14328ad62a feat(web-core): add stepper component (#9569) 2026-03-30 17:19:29 +02:00
Florent BEAUCHAMP
ac8dfeb7f5 feat(backups): improve incremental replication (#9635) 2026-03-30 17:04:16 +02:00
OlivierFL
bce3c88915 fix(xo-core): wrong translation key on PT locale (#9652)
Introduced by 658de40
2026-03-30 15:55:53 +02:00
DustyArmstrong
658de40f9e fix(web-core): fix some grammar issues on the XO 6 Dashboard (#9647) 2026-03-30 15:31:16 +02:00
Pierre Donias
b413db14e7 fix(xo-server-netbox): avoid mutating TYPES_WITH_UUID constant (#9607) 2026-03-30 15:24:58 +02:00
Enée DI IORIO
e6e5031964 feat(xo6/lite): add bug tools download button (#9419) 2026-03-30 14:35:45 +02:00
Florent BEAUCHAMP
484b9a02a3 docs(openmetrics): add documentation for xo metrics (#9631)
introduced by 9535
2026-03-30 14:34:32 +02:00
OlivierFL
a7829414b0 chore(web-core/query-builder): add query builder documentation (#9541) 2026-03-30 10:56:05 +02:00
vates-bot
39acc6b3a0 feat(web-stack/i18n): update Xen Orchestra 6 (#9554) 2026-03-30 10:55:00 +02:00
Mathieu
465c00b71d fix(xo-stack): return null instead of 0 for every NaN stats (#9634) 2026-03-27 17:26:55 +01:00
Mathieu
6814f6b2ef feat(qa-test): migrate xo-qatest into monorepo as @xen-orchestra/qa-test (#9626)
* feat(qa-test): migrate xo-qatest into monorepo as @xen-orchestra/qa-test
2026-03-27 14:35:03 +01:00
Elise-F
cc4cc1796b feat: technical release (#9646) 2026-03-27 14:22:03 +01:00
Florent BEAUCHAMP
1d81c0b26e fix(xapi): add missing package to changelog (#9645)
introduced by #9596
2026-03-27 13:46:38 +01:00
Elise-F
04a6fb3805 feat: technical release (#9642) 2026-03-27 10:23:50 +01:00
Florent BEAUCHAMP
1302618f90 fix(backup): compute the restore size for incremental restore (#9632)
introduced by #8432
2026-03-26 11:48:44 +01:00
Elise-F
7a39e45ffb feat(xo6): add please wait page for first connection in xo server (#9568) 2026-03-26 11:27:16 +01:00
Florent BEAUCHAMP
4bbfa77ec8 feat(xo5/sr): add preferred-image-format field (#9596) 2026-03-26 10:43:52 +01:00
Elise-F
57414dafce feat(xo6): implement vm snapshot tab page (#9521) 2026-03-26 10:03:19 +01:00
Elise-F
e28e768c72 Fix(xo6): remove scrollbar from console tab (#9609) 2026-03-26 09:14:24 +01:00
Joris K
8e5809a675 feat(xo6): add backup replication on vm dashboard (#9545) 2026-03-25 14:55:54 +01:00
Mathieu
56b17ce7f6 feat(rest-api): expose create internal and bonded network (#9611) 2026-03-25 11:25:06 +01:00
Mathieu
27a119f037 refactor(mcp): split monolithic index.mts into modular file structure (#9621)
XO-2221
2026-03-25 11:14:48 +01:00
Elise-F
60ba5070c9 fix(XO6): update watch to avoid reset template select in new VM (#9603) 2026-03-24 17:33:01 +01:00
Florent BEAUCHAMP
0efa078190 fix(backups): vhd directory have empty names (#9633) 2026-03-24 17:05:49 +01:00
Mathieu
c3dcb8fd50 feat(mcp): add list_srs and get_sr_details tools for SR (#9599)
* feat(mcp): add list_srs and get_sr_details tools for storage
repositories

* docs(mcp): add list_srs and get_sr_details to documentation

* fix(mcp): extract expected tool names into a constant in tests

* docs(mcp): add link to live filter syntax documentation for all filter
parameters
2026-03-24 16:39:58 +01:00
Florent BEAUCHAMP
0286f7786a fix(immutable-backups): decrease resource consumption, add tests and typing (#9548) 2026-03-24 16:00:01 +01:00
Florent BEAUCHAMP
f2b1931408 feat(disk-cli): implement disk cli tooling (#9551)
* feat(disk-cli): implement disk cli tooling

* changelog

* fix test path

* fix following review

* fix following rebase

* ci

* fix following rebase

* more tests
2026-03-24 14:35:05 +01:00
Florent BEAUCHAMP
f546849494 feat(xo-server): use index for getAllUnhealthyVdiChainsLength (#9622) 2026-03-24 10:37:18 +01:00
Florent BEAUCHAMP
6ae70a424a fix(backups): correctly rename replicated VMs (#9606) 2026-03-24 09:16:43 +01:00
Florent BEAUCHAMP
41751a8599 fix(backups): use the snapshot of the source (#9612)
* fix(backups): use the snapshot of the source

introduced by 9524

use only the vdi from the source VM as the source on backup
this bug could only occurs if the replication was intra pool, if
not the different xapi was filtering the results
2026-03-24 09:16:16 +01:00
Joris K
7ea8640ad0 fix(xo6/lite): fixed duplicated ip addresses in vm networks tab (#9547) 2026-03-23 16:29:41 +01:00
Enée DI IORIO
2994761178 fix(xo-lite/header): Fix menu button on large screen (#9558) 2026-03-23 10:12:56 +01:00
Mathieu
d1736e01f7 feat(mcp): add list_networks and get_network_details tools (#9595) 2026-03-20 09:38:59 +01:00
Mathieu
a2e3343b6e fix(rest-api/dashboard): exlude ISO SRs from dashboard (#9608) 2026-03-19 14:07:54 +01:00
Bastien Nollet
e325c6af51 docs(load-balancer): add info on migration cooldown (#9604) 2026-03-18 10:09:40 +01:00
Florent BEAUCHAMP
598ab85446 feat(vmware-explorer): make V2V import cancellable (#9552) 2026-03-17 18:17:08 +01:00
Pierre Brunet
1591749a23 fix(fs/s3): add check before using DeleteObjectsCommand (#9598)
fallback to delete object by object on multiple delete error  to handle backends that can't handle this command
2026-03-16 16:56:33 +01:00
Mathieu
800fd97996 feat(openmetrics): add missing VBD and DCMI metrics (#9563)
XO-1491
2026-03-16 11:37:19 +01:00
Pierre Brunet
45aee36ae7 typescript: Add types in several packages (#9583)
* feat: add types for fs (#9515)

* feat: add types for fs

* fix: update from review

* fix: update devDep

* feat: add types from @xen-orchestra/fs/path

* feat: export types based on async-each

* fix: remove ts-expect-error because async-each is now typed

* fix: export backups types and packages

* fix: update tsconfig async-each and fix types in MergeRemoteDisk

* fix: extractIdsFromSimplePattern from backups

* fix: package.json

* feat: export vhd-lib types

* feat(disks): export types

* fix(backups/disks): make params optional

* fix(backups/disks): small update in jsdoc

* bump: update yarn.lock

* fix: update vhd-lib exports

* fix(vhd-lib): update package exports

* fix: revert to previous import

* fix: new approach without exports

* fix: revert types except for asyncEach, openDisk

* fix(disks): import in vhd-lib

* fix(remoteVhdDisk): update typedef
2026-03-16 10:55:18 +01:00
Mathieu
2aff87d6d6 feat(mcp): add list_vdis tool to list virtual disks (#9559) 2026-03-13 15:54:40 +01:00
Mathieu
449e79de21 feat(rest-api): add disable/enable action endpoints for hosts (#9532) 2026-03-13 10:03:56 +01:00
MlssFrncJrg
9d657ac7cc feat(@xen-orchestra/rest-api): expose forget SR (#9505) 2026-03-12 16:12:43 +01:00
Enée DI IORIO
f4cc40233c fix(xo6/backups): backupLog transfer size calculation (#9496) 2026-03-12 15:09:19 +01:00
MlssFrncJrg
2cd5ba0319 feat(@xen-orchestra/rest-api): expose vms/:id/actions/clone (#9453) 2026-03-11 15:58:18 +01:00
Mathieu
2444861082 fix(rest-api/xo6): cpu at VM creation was not working (#9591)
* fix(rest-api/xo6): cpu at VM creation was not working

* changelog
2026-03-11 15:07:56 +01:00
Florent BEAUCHAMP
7ddff121de feat(fs): make part size configurable for s3 (#9561)
from https://xcp-ng.org/forum/topic/10587/s3-backup-maximum-number-of-parts/
2026-03-11 09:54:08 +01:00
Florent BEAUCHAMP
c1e9b83adc feat(backups): symetrical backups (#9524) 2026-03-11 09:34:09 +01:00
Mathieu
18d9b33c87 feat(mcp): support token authentication via XO_TOKEN (#9577) 2026-03-11 09:20:40 +01:00
Bastien Nollet
673e729afe fix(xo-server): incorrect import (#9589) 2026-03-10 19:25:55 +01:00
Florent BEAUCHAMP
4e0655dc3d fix(backups): better handling of generator cleanup (#9557) 2026-03-10 15:55:59 +01:00
bleader
a78870b473 docs(sdn-controller): mention xapi plugin is in BETA (#9556) 2026-03-10 15:42:16 +01:00
Thomas Moraine
0bf309a4ad docs(sdn_controller.md): clarify requirements for regenerating certificates (#9584) 2026-03-10 09:54:01 +01:00
spacotte-vates
5bdd76a955 feat: release 6.2.2 (#9579) 2026-03-09 14:41:09 +01:00
Pierre Brunet
e11530ea7c feat: technical release (#9576) 2026-03-09 10:10:36 +01:00
Florent BEAUCHAMP
c70f911b5e fix(changelog): remove package from release list (#9575)
introduced during rebase by #9572
2026-03-09 09:56:22 +01:00
Mathieu
82f047ef9d fix(xo-server): proxy issue if hostname defined (#9572)
fix #9500
2026-03-09 09:49:24 +01:00
Mathieu
57aec1ab2d fix(xo-server): race condition on XOA WebSocket during plugin loading (#9402) 2026-03-09 09:42:53 +01:00
Pierre Donias
385fcf3810 fix(xo-server/vm): allocate resource set quotas when not using DMC (#9566)
When editing VM RAM, the high-level `memory` field dispatches to `memoryMax` or
`_memory` depending on whether the VM uses DMC. Both paths need `limitName:
'memory'` on their spec so the resource set quota counter is correctly updated,
but `_memory` was missing it, causing memory resizes on non-DMC VMs to silently
create a stale `_memory` counter instead of updating the actual memory quota.
2026-03-09 09:37:24 +01:00
Florent BEAUCHAMP
6699bd1f5d fix(backups): remove snapshot of vm with cdrommounted (#9571) 2026-03-06 17:28:50 +01:00
Pierre Brunet
844c07e166 feat: technical release (#9570) 2026-03-06 14:49:29 +01:00
spacotte-vates
7676978c78 fix(backups): fix out of range error on merge (#9567)
Fixes out of range error during merge that was happening on delta backups when the child disk was bigger than the parent disk but no blocks where written in the "new" space.

Also added a test for this error.
2026-03-06 14:17:17 +01:00
Elise-F
7054fefdbd Style(xo6): update color for status paused VM icon (#9562) 2026-03-05 11:21:06 +01:00
Enée DI IORIO
acd84649ed feat(xo6): add backupRuns and BackupArchives card on vm dashboard (#9303) 2026-03-05 10:32:07 +01:00
Florent BEAUCHAMP
7460bf93d1 feat(openmetrics): add XO related metrics (#9535) 2026-03-04 10:30:20 +01:00
Mathieu
61e4a77cab fix(openmetrics): convert latency from µs to seconds instead of ms (#9550) 2026-03-03 10:01:56 +01:00
vates-bot
55130311f3 feat(web-stack/i18n): update Xen Orchestra 6 (#9531) 2026-03-03 09:18:35 +01:00
Florent BEAUCHAMP
f0cbd1bd72 docs(backup): document distributed backups (#9528)
* docs(backup): document distributed backups

* Apply suggestions from MPT code review

Co-authored-by: Mathieu <goore.csmoviz@gmail.com>

---------

Co-authored-by: Mathieu <goore.csmoviz@gmail.com>
2026-03-03 09:11:36 +01:00
Enée DI IORIO
561e2349e9 feat(xo6-lite/dashboard): add reactivity on resize (#9378) 2026-03-02 14:59:51 +01:00
Enée DI IORIO
7c83ed63f1 fix(web-core/treeItemLabel): fix treeview tooltip overlap (#9319) 2026-03-02 10:41:26 +01:00
Enée DI IORIO
9525abdee5 feat(xo6/Lite): add secureBoot support (#9423) 2026-02-27 15:09:49 +01:00
spacotte-vates
c6172b7b74 feat: release 6.2.1 (#9546) 2026-02-27 14:42:27 +01:00
Pierre Brunet
1b865bbf89 feat: technical release (#9544)
* feat(@xen-orchestra/backups): 0.69.2

* feat(@xen-orchestra/proxy): 0.29.49

* feat(xo-server): 5.197.3

* chore(CHANGELOG): update next

* fix: update changelog
2026-02-27 11:56:10 +01:00
spacotte-vates
815f4be76c fix(backups): changelog (#9543) 2026-02-27 11:44:13 +01:00
spacotte-vates
53f5c63fa2 fix(backups): race condition on merge (#9542) 2026-02-27 11:29:42 +01:00
spacotte-vates
4ffd1b4e13 feat(lite): 0.19.0 (#9540) 2026-02-26 18:29:55 +01:00
spacotte-vates
d25d15efd7 feat: release 6.2.0 (#9539) 2026-02-26 17:46:07 +01:00
Pierre Brunet
322205d203 feat: technical release (#9537) 2026-02-26 14:57:42 +01:00
Mathieu
89494457a1 fix(mcp): use bin wrapper to fix npx permission denied (#9536)
Fix npx @xen-orchestra/mcp failing with "Permission denied".

tsc does not preserve the executable bit on compiled files, so bin pointing to ./dist/index.mjs (mode 644) caused the error. This adds a bin/xo-mcp.mjs wrapper tracked by git with mode 755, following the same pattern as other packages (@xen-orchestra/xapi, @xen-orchestra/proxy, etc.).
2026-02-26 13:34:08 +01:00
Pierre Briffaux
6bc2844092 feat(xo6/new-vm): add custom user-config to new VM creation (#9427) 2026-02-26 13:15:30 +01:00
Mathieu
53c15bc9e4 docs(mcp) : documentation (#9529)
docs(mcp): add README and Docusaurus documentation page
2026-02-25 17:19:38 +01:00
Pierre Brunet
1dd8c1603f Release (#9534) 2026-02-25 16:19:54 +01:00
Mathieu
52513fc898 feat(xo-server): add transient option to host.disable (#9503)
Add optional transient parameter (boolean) to host.disable API endpoint.

When transient=true, passes auto_enable=false to XAPI, keeping the host disabled across reboots and toolstack restarts (requires XAPI >= 25.31.0). Default behavior unchanged (backward compatible).

XAPI 25.31.0 introduced a new auto_enable parameter to Host.disable (xen-api#6652). When set to false, the host stays disabled regardless of toolstack restarts, host reboots, or consider_enabling_host calls. Only an explicit Host.enable can re-activate it.
2026-02-25 15:54:26 +01:00
spacotte-vates
ed508ec04f feat: technical release (#9533)
* feat(@xen-orchestra/backups): 0.69.1

* feat(complex-matcher): 1.1.0

* feat(@xen-orchestra/web-core): 0.44.0

* feat(@xen-orchestra/mcp): 1.0.0

* feat(@xen-orchestra/proxy): 0.29.48

* feat(@xen-orchestra/rest-api): 0.26.0

* feat(@xen-orchestra/web): 0.42.0

* feat(xo-server): 5.197.1

* feat(xo-web): 5.194.0

* chore(CHANGELOG): update next

---------

Co-authored-by: Pierre Brunet <pierre.brunet@vates.tech>
Co-authored-by: Pierre Donias <pierre.donias@gmail.com>
2026-02-25 15:39:25 +01:00
MathieuRA
21823bf430 feat: add rest-api debug 2026-02-25 14:13:30 +01:00
Florent BEAUCHAMP
e36e1012e2 fix(xo-web): reducing polling for patches and license 2026-02-25 14:13:30 +01:00
Elise-F
3fcf582087 feat(new VM): add ssh key to vm creation form (#9413) 2026-02-25 10:24:29 +01:00
vates-bot
f1389cff6e feat(web-stack/i18n): update Xen Orchestra 6 (#9462) 2026-02-25 09:39:32 +01:00
Mathieu
438a7cc746 feat(mcp): add @xen-orchestra/mcp package (#9519)
This PR adds the new @xen-orchestra/mcp package — an MCP (Model Context Protocol) server that enables AI assistants (Claude Desktop, Claude Code, or any MCP-compatible client) to query a Xen Orchestra infrastructure in read-only mode.
2026-02-24 17:10:32 +01:00
Florent BEAUCHAMP
31e2ff1b02 feat(backups): only check size for full backups (#9527)
The size is computed from the blocksize * nb allocated blocks during the backups
which is not aligned with the real size on disk
Checking the file on disk is too costly for vhd directory
This will be eventually reimplemented by the next cleanVm rework
2026-02-24 17:04:58 +01:00
Thomas Moraine
44a23fb33f docs(sdn_controller.md): document openssl 3 + sdn upgrade path (#9525)
This pull request documents the manual steps that will be needed for the SDN controller, with the incoming March 2026 update of XCP-ng that will include OpenSSL3.
2026-02-24 13:21:26 +01:00
Pierre Donias
f8501c1a1e feat(xo-web/patches): warn about updating XOSTOR before installing patches (#9517) 2026-02-24 12:07:44 +01:00
spacotte-vates
6cab502260 feat(rest-api): sr scan route (#9514) 2026-02-24 11:05:18 +01:00
Pierre Donias
157e7fe757 feat(xo-web/licenses): display bundle name next to license name (#9512) 2026-02-23 16:20:46 +01:00
OlivierFL
0866a5db81 feat(xo-stack): implement Query Builder (#9488) 2026-02-23 15:35:23 +01:00
Pierre Donias
1c245acca5 feat(xo-web/network): reconfigure management PIF (#9510) 2026-02-23 14:36:18 +01:00
spacotte-vates
58c2c518a3 feat: technical release (#9523)
* feat(@xen-orchestra/fs): 4.6.7

* feat(@vates/types): 1.20.0

* feat(@xen-orchestra/backups): 0.69.0

* feat(@xen-orchestra/backups-cli): 1.1.8

* feat(@xen-orchestra/immutable-backups): 1.0.30

* feat(@xen-orchestra/web-core): 0.43.0

* feat(@xen-orchestra/proxy): 0.29.47

* feat(@xen-orchestra/rest-api): 0.25.0

* feat(@xen-orchestra/vmware-explorer): 0.11.0

* feat(@xen-orchestra/web): 0.41.0

* feat(xo-server): 5.197.0

* feat(xo-server-load-balancer): 0.12.0

* feat(xo-server-netbox): 1.11.0

* feat(xo-server-openmetrics): 1.2.0

* feat(xo-server-sdn-controller): 1.2.1

* feat(xo-web): 5.193.0

* chore(CHANGELOG): update next

---------

Co-authored-by: Pierre Brunet <pierre.brunet@vates.tech>
2026-02-23 10:23:57 +01:00
Elise-F
755ac108f2 feat(xo6): adds a banner when access to the xo server fails (#9375) 2026-02-20 17:12:29 +01:00
Bastien Nollet
f88c0e8be2 feat(rest-api): expose POST vdis (#9492) 2026-02-20 16:24:18 +01:00
Philippe Coval
109e37695c fix(sdn-controller): Support openssl-3 for ca/cert generation (#9507)
OpenSSL-3 (on xcp-ng) requires CA set to true in basicConstraints.

"All CAs should have the CA flag set to true."

For context, here are additional resources:

- https://docs.openssl.org/3.0/man1/openssl-verification-options/#basic-constraints
- https://docs.openssl.org/1.0.2/man1/x509v3_config/#basic-constraints
- https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9

To connect to OpenSSL 3, certificates should be re-generated if they
already exist (just remove xo-server/data/sdn-controller).

Renewed certs were tested on XCP-ng host on "OpenSSL 1.0.2k-fips"
without regression, so it is safe to assume those ext are designed to
be backward compatible.

Observed issue:

    openssl verify \
      -CAfile .../xo-server/data/sdn-controller/ca-cert.pem \
      .../xo-server/data/sdn-controller/client-cert.pem

    C=XX, L=Default City, O=Default Company LTD
    error 79 at 1 depth lookup: invalid CA certificate
    error .../xo-server/data/sdn-controller/client-cert.pem: \
      verification failed

The expected output is now:

    .../xo-server/data/sdn-controller/client-cert.pem: OK

The expected output is now:

    .../xo-server/data/sdn-controller/client-cert.pem: OK

It was tested along ovsdb-server (Open vSwitch) 2.17.7 :

    openssl s_client \
      -connect  10.1.75.62:6640 \
      -cert .../xo-server/data/sdn-controller/client-cert.pem \
      -key .../xo-server/data/sdn-controller/client-key.pem

    (...)
    CONNECTED(00000003)
    Can't use SSL_get_servername
    (...)
    verify error:num=18:self-signed certificate
    (...)
    SSL handshake has read 2713 bytes and written 3799 bytes
    Verification error: self-signed certificate
    (...)
    New, TLSv1.2, Cipher is AES128-GCM-SHA256
    (...)
    {"id":"echo","method":"echo","params":[]}

Extra info at:

- https://xapi-project.github.io/new-docs/design/pool-certificates/

Origin: https://github.com/vatesfr/xen-orchestra/pull/9507
Relate-to: XCPNG-2710
Relate-to: XO-1986
Signed-off-by: Philippe Coval <philippe.coval@vates.tech>
2026-02-20 09:34:13 +01:00
spacotte-vates
a4b480de4a feat(rest-api): add relaim_space action to sr (#9486) 2026-02-19 17:26:12 +01:00
spacotte-vates
db8addbc45 fix(backups): fix RemoteVhdDisk & DiskChain parent and directory issue (#9508)
* fix(backups): fix RemoteVhdDisk & DiskChain parent and directory issue
2026-02-18 14:06:05 +01:00
Sebastien Marie
9dbc090116 fix(xo-server-sdn-controller): communicate with enabled hosts only (#9442)
permit to have working traffic rules when a pool is in a 'degraded' mode (for
example, one host temporary down or unreachable).

currently, exceptions occurs and nothing happens (no rules added or deleted).
with it, unreachable hosts are ignored and reachable hosts properly updated.
2026-02-17 13:31:44 +01:00
OlivierFL
353678e754 feat(xo-6/internal-links): normalize internal links (#9276) 2026-02-17 08:20:01 +01:00
Florent BEAUCHAMP
a313986694 feat(backups): implements distributed backup and replications (#9433)
The deletion of old backups is done globally
For Full backups/replication:
 * retention is computed on the total of the target
 * choose the storage with the most free size ( if possible, since some backup repositories don't have any size used/ free )
 * one vm is written on the same target ( xva, index, checksum )
 * healthcheck ( if enabled ) will run only on the target that received the data
 * the cleanup of old backups/replication is done on the right target ( but considering the full lenght across all the targets )

For incremental: 
 * delta chain are continued on the same target 
 
For mirror : 
if a vm has multiple backups to transfer during the same run, then
they will all go to teh same remote
 
UX : 
* show a toggle on remote/sr selectors
* toggle is disabled if 1 or less targets are selected
* for backup and mirror backup forms
* show the details in the job list
  

More details on the PR page
2026-02-16 18:17:03 +01:00
Florent BEAUCHAMP
983ac2d2c9 docs(config): explain the config files priorities (#9463) 2026-02-16 18:11:50 +01:00
spacotte-vates
4e50858526 feat(@xen-orchestra/backups): refactor backup merge code (#9300)
this merge code leverage the new classes introduced to handle qcow2, improving our flexibility, and future uses.

this also improve the performance on "block mode" backups (s3/ azure and other storage with the option), since it reliably updates the block allocation table only at the end of the merge instead of each time a new block is detected
2026-02-16 18:04:12 +01:00
Mathieu
c497ae3d38 fix(web): use the store's object type and not type from vates/types (#9421) 2026-02-16 17:01:26 +01:00
spacotte-vates
9d9b5d5e33 feat(rest-api): add plug and unplug pbd routes to rest api (#9477) 2026-02-16 16:52:04 +01:00
OlivierFL
5834070e77 fix(xo-6/xo-lite): missing required prop for UiRadioButtonGroup in new VM creation form (#9504) 2026-02-16 16:50:40 +01:00
Enée DI IORIO
5c5f27f92b fix(xo-stack): fix VM creation on small screen (#9344) 2026-02-16 16:16:11 +01:00
Cyrille G.
8fc04bc03b fix(hub/recipe): remove DHCP option for the Kubernetes recipe (#9454) 2026-02-16 10:20:11 +01:00
Pierre Brunet
87135c7469 feat: add qcow2 support in vdi creation (#9493)
Add qcow2 image-format when size is higher than 2TB. It will still be a VHD if PBD.preferred-image-formats do not list qcow2
2026-02-16 09:56:08 +01:00
Mathieu
fb030a590e docs(openmetrics): update documentation for host uptime, host status and is_control_domain (#9497)
docs(openmetrics): update documentation for host uptime, host status and
is_control_domain
2026-02-16 09:55:02 +01:00
Florent BEAUCHAMP
0be2392519 feat(xo-server/V2V): automatically take a snapshot if needed (#9471) 2026-02-12 17:07:56 +01:00
Florent BEAUCHAMP
546d42427c feat(xo-web): remember V2V connections settings (#9490) 2026-02-12 15:42:47 +01:00
Mathieu
fc86fda13d fix(rest-api): exclude removable storage from top 5 SRs usage (#9495) 2026-02-12 14:04:12 +01:00
Elise-F
b44bed6d10 feat(web-core): new ui rich radio button component created (#9198) 2026-02-12 11:53:35 +01:00
Mathieu
4e66889233 fix(xo-server-openmetrics): bind to localhost instead of 127.0.0.1 (#9489) 2026-02-12 09:51:42 +01:00
Pierre Donias
10bb1741e7 chore: split changelog file (#9390) 2026-02-11 14:57:35 +01:00
Mathieu
a0f890f301 feat(openmetrics): add host uptime metric (#9449) 2026-02-11 11:08:43 +01:00
Pierre Donias
839a484017 fix(xo-web/host/patches): remove obsolete yum install command instruction (#9451) 2026-02-11 10:34:02 +01:00
MlssFrncJrg
dc9e029bdb feat(netbox): support Netbox v4.5.x (#9445)
Fixes #9379
See https://github.com/netbox-community/netbox/releases
2026-02-11 10:31:47 +01:00
Mathieu
f203b1f1e1 feat(rest-api): expose vms/:id/actions/migrate (#9414) 2026-02-11 09:55:51 +01:00
Mathieu
7399a9fec5 feat(openmetrics): add xcp_host_status metric (#9457) 2026-02-10 16:39:40 +01:00
Olivier Lambert
c69b2336f6 feat(xo-server-load-balancer): add migration cooldown to prevent VM flapping (#9388) 2026-02-10 16:11:27 +01:00
Florent BEAUCHAMP
80ab29de29 feat(xo-server): warm migration works with non running VM 2026-02-10 16:08:10 +01:00
Florent BEAUCHAMP
1449a8482e feat(xo-server): vm.warmMigration now return the uuid 2026-02-10 16:08:10 +01:00
Mathieu
ec9806e081 feat(rest-api): add objectType to tasks from actions (#9429) 2026-02-10 15:22:03 +01:00
Pierre Brunet
c6c31524e1 build: bump all dependencies (#9481) 2026-02-10 14:42:37 +01:00
Mathieu
49fce9b61f feat(openmetrics): add is_control_domain label to differentiate dom0 VMs (#9474) 2026-02-10 14:41:42 +01:00
Thomas Moraine
f52a35c093 docs(metadata_backup): clarify the two types of metadata backups (#9487) 2026-02-10 14:11:42 +01:00
Mathieu
9d74585bd4 fix(@xen-orchestra/web): fix build issue (#9482)
Introduced by #7851
2026-02-10 10:59:37 +01:00
Mathieu
12f4360100 feat: release 6.1.2 (#9485) 2026-02-10 10:01:17 +01:00
Pierre Brunet
771ce8c6d5 fix(backups/s3): add fallback if deleteObjects unsupported (#9450) 2026-02-10 09:29:53 +01:00
Mathieu
41c9a6c7c2 feat: release 6.1.2 (#9480) 2026-02-09 09:43:51 +01:00
Florent BEAUCHAMP
7e08a016bd fix(backups): ensure vm created by backups are removable by backups : halthcheck/replication (#9473) 2026-02-06 15:15:37 +01:00
Mathieu
91c5d98489 feat: release 6.1.1 (#9470) 2026-02-05 16:59:00 +01:00
Mathieu
b895f48a4f feat: technical release (#9469) 2026-02-05 16:01:47 +01:00
Mathieu
d68d878747 fix(changelog): add missing package to release (#9468) 2026-02-05 15:46:55 +01:00
Mathieu
5155a9d5f1 feat: technical release (#9467) 2026-02-05 14:58:41 +01:00
Florent BEAUCHAMP
7437427fae fix(backups): refresh the snapshot name after removing the temp name (#9466)
introduced by #9434
2026-02-05 13:13:39 +01:00
spacotte-vates
a16b4cb540 feat(rest-api): add post vifs and delete vifs/:id routes to rest api (#9393) 2026-02-05 12:05:26 +01:00
Florent BEAUCHAMP
21508c97bc fix(immutable-backups): really protect pool metadata
Apply suggestion from @MathieuRA

Co-authored-by: Mathieu <70369997+MathieuRA@users.noreply.github.com>

Apply suggestion from @MathieuRA

Co-authored-by: Mathieu <70369997+MathieuRA@users.noreply.github.com>
2026-02-05 10:16:03 +01:00
Florent BEAUCHAMP
e8ff60c8bc docs(immutable-backups): fix app name in documentation
alos changelog that wasn't in previous PR
2026-02-05 10:16:03 +01:00
Mathieu
153efccb85 fix(rest-api): pools/:id/actions/create_vm install.repository was ignored (#9461) 2026-02-05 10:10:39 +01:00
Joris K
a99e1aa72d fix(xo6/lite): only count running vms for cpu provisioning in host dashboard (#9459) 2026-02-05 09:27:20 +01:00
Mathieu
dd5f0cdaa9 fix(rest-api): fix backup-archives href (#9460)
* fix(rest-api): fix backup-archives href

* changelog

---------
2026-02-04 17:10:37 +01:00
Mathieu
ea142b50a4 fix(rest-api): only count running vms for cpu provisioning in pool dashboard (#9456)
See Forum#101359
2026-02-04 17:05:16 +01:00
Pierre Briffaux
5b5a0a72cc fix(web-core): enable legacy clipboard support for http (#9426) 2026-02-04 16:41:16 +01:00
Joris K
84a16ef85f fix(xo6): fix issue with alarms display (#9458) 2026-02-04 11:18:38 +01:00
vates-bot
b983e6d4fa feat(web-stack/i18n): update Xen Orchestra 6 (#9440) 2026-02-04 10:40:27 +01:00
Joris K
a0e771f09c feat(xo6): update site dashboard (#8964) 2026-02-03 17:51:06 +01:00
Florent BEAUCHAMP
9bd91aea43 feat(fs): autodetect object lock/immutability on s3 (#9431)
* add a setting to declare remote immutable ?immutable=true in the URL 
* autodetect object lock/immutabiltiy on s3
2026-02-03 17:12:15 +01:00
Florent BEAUCHAMP
0a042d2f54 fix(xapi): correctly block operations
blocked operation on vm creation does not seem to work

also importIncrementalVm was not unlocking it at the end
2026-02-03 15:59:24 +01:00
Florent BEAUCHAMP
c610951450 fix(backups): set other_config on incremental replication vm creation
this will ensure that any partial transfer will ben cleaned on next run
2026-02-03 15:59:24 +01:00
Florent BEAUCHAMP
a831ac7b21 chore(backups): remove old todo 2026-02-03 15:59:24 +01:00
Florent BEAUCHAMP
d0f71d4ec7 fix(backups): don't leave dangling snapshots (#9434)
when the connection to the host break between the snapshot() and
the update of other config
this could leave snapshot that will never be cleaned by any backup job
The fix is that the snapshot will be named with a specific name, it
will cause an issue if a user reuse this name
2026-02-03 15:55:11 +01:00
Joris K
c1a9bc82fb feat(xo6/vm): add more actions button to delete VM and take snapshot (#9410) 2026-02-03 13:05:49 +01:00
Dan Pollak
601cc3e9fd fix(immutable-backups): Fix typo in sample config file (#9444)
* fix(immutable-backups): default config.toml missing quotes

Signed-off-by: Dan Pollak <danpollak2@gmail.com>

* fix(immutable-backups): Locate config file

Signed-off-by: Dan Pollak <danpollak2@gmail.com>

* Update CHANGELOG

Signed-off-by: Dan Pollak <danpollak2@gmail.com>

* Revert "fix(immutable-backups): Locate config file"

This reverts commit e9e367474d.

* Update CHANGELOG.unreleased.md

Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@gmail.com>

---------

Signed-off-by: Dan Pollak <danpollak2@gmail.com>
Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@gmail.com>
2026-02-03 13:03:09 +01:00
MathieuRA
79d1f42dd7 fix(rest-api): do not create auth task when using auth token 2026-02-03 09:20:25 +01:00
MathieuRA
64c366838a fix(rest-api): don't send message updates when subscribing to alarm 2026-02-03 09:20:25 +01:00
MathieuRA
85b3162be6 fix(rest-api): set memory limit per sse client 2026-02-03 09:20:25 +01:00
Mathieu
eb020e250b docs(openmetrics): add SR capacity metrics and complete VM metrics (#9438)
documentation
2026-01-30 16:50:55 +01:00
Olivier Lambert
56142085e5 feat(xo6/settings): add various themes (#9387) 2026-01-30 15:21:00 +01:00
Mathieu
2838bde875 feat(lite): 0.18.0 (#9437) 2026-01-29 17:56:53 +01:00
Mathieu
fa110ed9c9 feat: release 6.1.0 (#9436) 2026-01-29 17:31:28 +01:00
Mathieu
d3187c06b5 feat: technical release (#9432) 2026-01-29 10:57:33 +01:00
Florent BEAUCHAMP
2f41ccffe8 feat(xapi): try to disconnect vdi if a vm_destroy fail (#9415)
happenned during a backup for HK
only solve case where the disk can be manually disconnected from DOM0
2026-01-29 10:02:59 +01:00
Elise-F
a9e45fcb8e feat(xo6/Lite): add VTPM support (#9389) 2026-01-29 09:46:41 +01:00
Pierre Briffaux
1e69c25ba5 fix(xo6/new-vm): fixed disk naming from template (#9294) 2026-01-28 16:13:58 +01:00
Joris K
9f852365fc feat(xo6/vm): add VMs actions to manage lifecycle (#9403) 2026-01-28 16:01:57 +01:00
Enée DI IORIO
05824cb8a6 feat(web-core): normalize icons creation from design system (#8989) 2026-01-28 15:29:40 +01:00
vates-bot
638ee77aff feat(web-stack/i18n): update Xen Orchestra 6 (#9330) 2026-01-28 10:36:59 +01:00
Bastien Nollet
08a560a8da fix(xo-server): fix NFSv4 ISO SR creation (#9425) 2026-01-28 10:02:44 +01:00
Elise-F
e77a0e43f1 feat(xo6): remove text ellipsis on sides panel (#9238) 2026-01-27 18:29:35 +01:00
Florent BEAUCHAMP
8069adf26d feat(xo-server): expose an api method to export a disk from vmware 2026-01-27 16:04:05 +01:00
Florent BEAUCHAMP
1241977599 fix(vhd-cli): raw need to load BAT before working 2026-01-27 16:04:05 +01:00
Florent BEAUCHAMP
4ca314fbc7 fix(nbd-client): discrepency when using hasblock/getblockindexes 2026-01-27 16:04:05 +01:00
Mathieu
13ab1cce86 feat(rest-api): add POST /vdis/{id}/actions/migrate endpoint (#9408) 2026-01-27 10:58:28 +01:00
Mathieu
9f7797fdda feat(rest-api): add VBD connect/disconnect endpoints (#9399) 2026-01-27 10:25:04 +01:00
Mathieu
21767145bd feat: technical release (#9422) 2026-01-23 14:44:23 +01:00
OlivierFL
6805e16108 refactor(xo6): project architecture (#9371) 2026-01-23 12:02:43 +01:00
Mathieu
ecd49e69a7 fix(xo-server-openmetrics): add sr_name label to VM disk metrics (#9353)
Introduced by 0005a4f4cc1990e9a20eb48becb5943b5bf15c43
2026-01-23 10:24:29 +01:00
Florent BEAUCHAMP
a5f5a0273e fix(boackups): reverting a VM from a backup shouldn't leave dangling other_config (#9398) 2026-01-23 10:18:52 +01:00
Mathieu
ddc9bf3211 feat(rest-api): add DELETE /vbds/{id} endpoint (#9394) 2026-01-22 14:31:25 +01:00
Mathieu
75db97f9ea feat(@vates/types,xo-server-openmetrics): use centralized XoApp type (#9404)
XO-1810
2026-01-22 11:19:49 +01:00
Mathieu
07c1c02f03 feat(rest-api): add secureBoot param for vm creation (#9417) 2026-01-22 11:13:42 +01:00
Mathieu
dbe8e0ce26 fix(rest-api): vms/:id/start fix request body being ignored (#9416)
introduced by 0202b56d33
2026-01-21 17:42:20 +01:00
Florent BEAUCHAMP
82cd861479 feat(backups): expose LTR tags to VM backups (#9364) 2026-01-21 17:34:50 +01:00
Pierre Briffaux
32f8c1a25f feat(xo-lite): scroll to selected item in treeview (#9332) 2026-01-21 16:52:06 +01:00
Florent BEAUCHAMP
9e7813319a fix(vhd-lib/qcow2): better handling of unaligned blocks (#9407) 2026-01-21 15:52:33 +01:00
Mathieu
caba00a2b9 feat(rest-api): add createVtpm parameter to VM creation endpoint (#9412) 2026-01-21 15:25:03 +01:00
Pierre Briffaux
927561123a feat(xo6/treeview): scroll to selected item in treeview (#9268) 2026-01-21 11:51:25 +01:00
Mathieu
097c2bd1d9 feat(rest-api): add POST /vbds endpoint to create VBDs (#9391) 2026-01-20 14:36:24 +01:00
MlssFrncJrg
5288b6d848 feat(xo6/vm): add 'Change State' button on VM page (#9317) 2026-01-20 11:54:01 +01:00
Pierre Brunet
263059be22 fix: update node-tar to handle CVE-2026-23745 (#9406)
* fix: update node-tar to handle https://github.com/advisories/GHSA-8qq5-rm4j-mr97

* chore: update changelog
2026-01-20 11:42:57 +01:00
Florent BEAUCHAMP
c83df8a342 fix(backups): more filtering on disk snapshot before starting (#9405)
from ticket 33058
2026-01-19 11:20:09 +01:00
Bastien Nollet
4f959e2d22 fix(backup-reports): prevent succesful backups being sometimes repor… (#9400)
* fix(backup-reports): prevent succesfull backups being sometimes reported as interrupted

* increase delay after user feedback

* change comment according to delay change

---------

Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@vates.tech>
2026-01-19 11:06:36 +01:00
Florent BEAUCHAMP
ee6df8c979 chore(backups): remove unused and broken code (#9367)
note the missing await for the adapter._handler.readFile
this method is correctly implemented in MixinRemoteWriter
2026-01-19 10:58:16 +01:00
Mathieu
0a0ae5dc7e feat(xo-server/rest-api): add management interface reconfigure API (#9369) 2026-01-14 16:29:43 +01:00
Mathieu
58b6215c41 fix(@xen-orchestra/rest-api): check length instead of undefined (#9401)
Introduced by #9395
2026-01-14 15:17:59 +01:00
Mathieu
0e56c5e2bd fix(@xen-orchestra/rest-api): add isEmpty prop for xoa dashboard (#9395) 2026-01-14 11:27:28 +01:00
Florent BEAUCHAMP
0b44b1b52b fix(backups): size estimation of full backups (#9396) 2026-01-14 10:38:52 +01:00
Bastien Nollet
4c7b8b194f feat(vhd-cli): prevent using invalid options (#9386) 2026-01-13 16:52:36 +01:00
Mathieu
ac2ed8880a feat(@vates/types): expose XoApp (#9322) 2026-01-12 15:27:53 +01:00
Raven
6fc30c899d docs(xo): Add instructions for fetching deps on RHEL 10 distros (#9383)
RHEL 10 and co. (AlmaLinux, Rocky, CentOS) stopped shipping redis and
moved some packages around. Update the docs to reflect that.
2026-01-12 11:56:48 +01:00
Florent BEAUCHAMP
05e1298eec fix(backups): rework snapshot listing (#9377) 2026-01-12 08:17:54 +01:00
Mathieu
d20561fadc fix(xo-server-openmetrics): use correct timestamp property name in computeVmCpuUsageFallback (#9384)
Introduced by 29fa4b2
2026-01-09 14:12:28 +01:00
Mathieu
53a3e2a70f feat(xo-server-openmetrics): add SR capacity metrics (#9360)
* feat(xo-server-openmetrics): add SR capacity metrics

* fix(xo-server-openmetrics): refactor SR types and add host labels for
local SRs

---------

Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@vates.tech>
2026-01-09 10:25:10 +01:00
Mathieu
2c90eb5ba8 fix(rest-api): string and not 'string' (#9382) 2026-01-09 10:24:37 +01:00
Mathieu
c3e78ebdfd fix(rest-api): fix getVmVdis and enhance the type (#9381) 2026-01-09 10:24:20 +01:00
Mathieu
264b93d502 feat(open-metrics): compute vm_cpu_usage from per-core metrics (#9331)
* feat(open-metrics): compute vm_cpu_usage from per-core metrics

* refactor(xo-server-openmetrics): combine conditional checks in
computeVmCpuUsageFallback
2026-01-09 10:23:26 +01:00
Elise-F
d0abf8810c feat(web-core): update UI title component (#9292) 2026-01-09 10:09:25 +01:00
Mathieu
bdd9b26651 fix(rest-api): various vm dashboard fixes (#9380) 2026-01-08 17:30:12 +01:00
Elise-F
66d5014755 feat(xo6): removal borders of sidebar in mobile (#9366) 2026-01-08 16:49:22 +01:00
mathieuRA
8e2d9f1022 feat(xo6): use define-job for server creation 2026-01-07 16:04:21 +01:00
mathieuRA
566bdeda63 feat(xo6): use define-job for VM creation 2026-01-07 16:04:21 +01:00
mathieuRA
e86d71fc31 feat(xo6): implement async action monitoring 2026-01-07 16:04:21 +01:00
Mathieu
4827f33305 feat(@xen-orchestra/rest-api): update xoa dashboard endpoint (#9207)
XO-1462
2026-01-07 14:39:48 +01:00
Elise-F
9b6d1089f4 feat(web-core): update text logo with custom font (#9298) 2026-01-06 16:01:34 +01:00
Elise-F
d1f8f61791 feat: release 6.0.3 (#9374) 2026-01-06 15:20:32 +01:00
Elise-F
dbf00ba0cd feat: technical patch release (#9373) 2026-01-06 14:33:41 +01:00
Pierre Brunet
44a0d14e88 fix (XO Hub): read getResourceCatalog removed during refactor (#9362) 2026-01-06 14:08:48 +01:00
Elise-F
0b52a05bb3 feat: technical patch release (#9372) 2026-01-06 11:51:13 +01:00
Florent BEAUCHAMP
407591d74d fix(vhd-lib): more logs on backup edge cases (#9326)
* more logs for vdi attached to multiples VMs failing _removeUnusedSnapshots
* more logs for incomplete reading of a disk
2026-01-06 10:57:41 +01:00
Enée DI IORIO
0b29586626 fix(xo6/site-dashboard): replace raw text by translated text in Pools Status (#9329) 2026-01-05 16:38:07 +01:00
Florent BEAUCHAMP
42eb8664be fix(xo-server/v2v): more block alignment fixes (#9293)
from #45336
2026-01-05 16:01:59 +01:00
Florent BEAUCHAMP
1d8c429532 fix(backups): cleanup data on restore (#9361) 2026-01-05 12:10:44 +01:00
Joris K
c51df2239f fix(xo6/host): correction of a translation key (#9359) 2026-01-05 10:34:06 +01:00
Mathieu
b89c26459c feat: release 6.0.2 (#9356) 2025-12-23 15:41:32 +01:00
Mathieu
a1333c07d7 feat: technical release (#9355) 2025-12-23 14:45:33 +01:00
Mathieu
5e8e5beba1 fix(xo-server): fix netdata url (#9354) 2025-12-23 14:04:08 +01:00
Mathieu
9169e11f35 fix(xo-server): fix don't call the updater with secure protocol (#9349) 2025-12-23 11:46:28 +01:00
Mathieu
6908001bd8 fix(xo-server): bypass session auth for /openmetrics routes 2025-12-22 18:02:29 +01:00
Mathieu
53ec9a62a8 fix(xo-server-openmetrics): comply with OpenMetrics 1.0 specification 2025-12-22 18:02:29 +01:00
Joris K
4a0590b7e9 fix(web core): fix task item tree (#9352)
Introduced by 3eb9efe

Fix an issue on taskitem tree only on firefox
2025-12-22 11:22:10 +01:00
Thierry Goettelmann
d358aed897 fix(web-core/lite): UiInput v-model modifiers and reactivity (#9282)
Fixes randomly blank input
2025-12-22 11:03:15 +01:00
Florent BEAUCHAMP
5fcb6db32e docs(xo5): use absolute path (#9347) 2025-12-19 17:29:54 +01:00
Mathieu
1de60c52b4 docs(openmetrics): add user documentation for Prometheus integration (#9333) 2025-12-19 14:17:38 +01:00
Elise-F
55243b65a2 feat: release XO 6.0.1 (#9345) 2025-12-19 14:03:18 +01:00
Elise-F
0247b3b9b3 feat: technical release (#9343) 2025-12-19 11:48:25 +01:00
Mathieu
6dae628e0d Fix/openmetrics hardcode server config (#9342)
* docs(changelog): add entry for xo-server-openmetrics security fix

* Apply suggestion from @fbeauchamp

---------

Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@vates.tech>
2025-12-19 11:28:51 +01:00
Mathieu
7827b27d63 fix(xo-server): fix http.proxies when redirectToHttps=true (#9339)
introduced by 7c1764a39e
fix [forum#11681](https://xcp-ng.org/forum/post/100852)
2025-12-19 11:11:35 +01:00
Mathieu
86ddff46a9 fix(xo-server-openmetrics): hardcode port and bind address for security (#9337) 2025-12-19 10:35:50 +01:00
Pierre Donias
476f4e5f1f fix(xo-server/signin-otp): pass xo5Mount param to Pug template (#9340) 2025-12-19 10:12:24 +01:00
Elise-F
8448b4d5a6 feat: release 6.0.0 (#9336) 2025-12-18 16:36:07 +01:00
Elise-F
28952a0a4a feat: technical release (#9334) 2025-12-18 12:21:33 +01:00
Pierre Brunet
19f973fcd3 fix: replace deprecated endpoint call (#9316)
Call to deprecated route restore/logs replaced by restore-logs/
2025-12-18 09:20:48 +01:00
OlivierFL
d9fe9b603f fix(xo-6/account-menu): license url was not redirecting properly to XO 5 (#9321) 2025-12-17 17:49:46 +01:00
OlivierFL
13a462429e fix(xo-6/xo-lite/new-vm-form): change "memory" section to "resource management" for better clarity (#9309) 2025-12-17 17:45:08 +01:00
Mathieu
bfe788e096 fix(@xen-orchestra/web-core): fix race condition on reactive store (#9307)
Introduced by 5d28710ab3
2025-12-17 17:37:15 +01:00
Mathieu
980e4b5887 Feature openmetrics (#9323) 2025-12-17 17:17:53 +01:00
Florent BEAUCHAMP
0d8cb990c6 fix(xen-api): don't eat relevant error during import (#9327)
don't eat relevant error during import

SR contains the full list of the VDI on the SR
host contains the full list of the VM on the HOST
2025-12-17 16:40:30 +01:00
vates-bot
7794adfb89 feat(web-stack/i18n): update Xen Orchestra 6 (#9305) 2025-12-17 16:14:27 +01:00
Mathieu
fb67e8eea7 fix(xo-server): report 0 RAM usage for halted hosts (#9272) 2025-12-17 16:01:17 +01:00
Pierre Brunet
1316a811f5 feat(plugins/backup-report): add custom subject in plugin settings (#8973) 2025-12-17 15:41:11 +01:00
Joris K
c6f47f9ef3 feat(xo6/vm): implement tasks view and side panel information (#9313) 2025-12-17 15:31:45 +01:00
Joris K
c2a95467f8 feat(xo6/host): implement tasks view and side panel information (#9311) 2025-12-17 15:25:45 +01:00
Joris K
4d58ff885a feat(xo6/pool) implement tasks view and side panel information (#9312) 2025-12-17 15:15:08 +01:00
Florent BEAUCHAMP
3892eee7a0 fix(xo-server): changelog after xosan removing (#9302) 2025-12-17 15:01:33 +01:00
Joris K
15b9e13ed2 feat(xo6/site): implement tasks view and side panel information (#9063) 2025-12-17 14:50:34 +01:00
Mathieu
e504b8af0f fix(@xen-orchestra/rest-api): fix duplicated entries with ndjson (#9320) 2025-12-17 12:02:22 +01:00
Mathieu
efc751c54e fix(xo-server): check certificate instead port to define secure protocol (#9324) 2025-12-17 11:35:50 +01:00
Mathieu
8b4c953ab0 docs(configuration): update doc for XO5 by default (#9296) 2025-12-17 09:44:50 +01:00
OlivierFL
32b3c0b5fc refactor(web-stack/translations): remove duplicates, improve pluralization, improve actions, clean translations (#9295) 2025-12-16 15:51:57 +01:00
Mathieu
7c1764a39e fix(xo-server): fix proxied urls when using ssl (#9314)
introduced by 03cea8b24d

Fixed [forum#11681](https://xcp-ng.org/forum/topic/11681/xo5-breaks-after-defaulting-to-xo6-from-source/16?_=1765875445829)
2025-12-16 11:23:54 +01:00
Pierre Brunet
b248cddece fix: perf-alert unload (#9306) 2025-12-16 10:19:50 +01:00
Florent BEAUCHAMP
df2dc3a710 fix(xapi): don't fail detecting image format on suspend disk (#9308)
from #48758

suspend_vdi  disk created for snapshot with memory are snapshot
but don't have a parent, thus failing the check 

introduced by c870bb653a
2025-12-15 17:08:26 +01:00
Elise-F
227063341e feat: technical release (#9310) 2025-12-15 16:00:02 +01:00
Mathieu
a4ab2e8b06 feat(@xen-orchestra/rest-api): add reactivity on task store (#9271) 2025-12-15 10:51:58 +01:00
Mathieu
9f3872a5f1 feat(@xen-orchestra/rest-api): ability to subscribe to task changes (#9269) 2025-12-12 18:07:39 +01:00
mathieuRA
e2d727abe5 fix(@xen-orchestra/rest-api): fix cannot read property of undefined on VM dashboard 2025-12-12 17:27:35 +01:00
mathieuRA
11720d96f1 fix(@xen-orchestra/rest-api): fix cannot convert undefined or null to object 2025-12-12 17:27:35 +01:00
vates-bot
859ebb892d feat(web-stack/i18n): update Xen Orchestra 6 (#9243) 2025-12-12 13:06:18 +01:00
Pierre Brunet
035ee8fb8e refactor: remove all xosan components (#9248)
XOSAN is not used anymore by clients. We can safely remove it from code base to clean it up.
2025-12-11 17:14:04 +01:00
Mathieu
bc69046bdd feat: technical release (#9301) 2025-12-11 15:22:37 +01:00
spacotte-vates
debde3f16e fix(xo-server): fix guiRoute generation (#9299) 2025-12-11 15:09:08 +01:00
Joris K
3eb9efe1f2 feat(web-core): add UiTaskItem and UiTaskList components (#9009) 2025-12-10 17:18:57 +01:00
Mathieu
7cf9726876 feat: technical release (#9297) 2025-12-10 15:08:46 +01:00
Pierre Briffaux
64d9f8c39e feat(xo6): update links to XO-5 (#9214) 2025-12-10 14:38:02 +01:00
Mathieu
03cea8b24d feat(xo-server): set XO6 the default page (#9212) 2025-12-10 14:29:49 +01:00
Bastien Nollet
4b4c9b91f3 fix(xo-web): allow offline delta backups (#9228) 2025-12-10 13:19:09 +01:00
Mathieu
907ee7d88c feat(@xen-orchestra/web): display modal of the first connection (#9230) 2025-12-10 12:52:01 +01:00
Enée DI IORIO
dbdc4cac4a fix(web-core): fix pagination placement (#9264) 2025-12-10 10:16:33 +01:00
Bastien Nollet
19c0b958fc fix(backups): fix incorrect info on mirror backups (#9286)
From forum#11623
2025-12-10 09:39:03 +01:00
Florent BEAUCHAMP
e5d2e83de6 fix(backups): cleanup .vhd.checksum files (#9291) 2025-12-10 09:19:58 +01:00
spacotte-vates
85596da792 fix(xo-server): improve handling of xapi snapshots (#9231)
In some cases, when converting an xapi object into an xo object, the xapi.is_a_snapshot param may be set to false even tho the xapi.snapshotOf refers to another xapi object. Handle this case better.
2025-12-10 09:16:18 +01:00
Pierre Brunet
230749a3f3 feat(fs/s3): DeleteObjectsCommand (#9281) 2025-12-10 09:08:35 +01:00
Bastien Nollet
b667bc8a23 fix(@xen-orchestra/fs): avoid retrying on some statuses (#9188) 2025-12-09 17:50:32 +01:00
Mathieu
76abf1ed73 docs(restapi.md): update REST API docs (#9284) 2025-12-09 14:18:51 +01:00
Mathieu
54930ee07f fix(@xen-orchestra/rest-api): vmProtected -> vmProtection (#9288) 2025-12-09 11:53:18 +01:00
Mathieu
90295ca924 feat(@xen-orchestra/web): redirect non admin to xo5 (#9219) 2025-12-09 11:48:04 +01:00
Mathieu
e281c536d3 feat: release 5.113.2 (#9290) 2025-12-09 11:32:27 +01:00
Mathieu
efa5b1a71e feat(@xen-orchestra/rest-api): display URL of the openApi spec (#9285) 2025-12-09 11:10:10 +01:00
Mathieu
62aaa21ced feat: technical release (#9289) 2025-12-09 10:19:09 +01:00
Florent BEAUCHAMP
dfbf654159 fix(xo-server-perf-alert): improve email alert subject (#9283) 2025-12-08 18:03:26 +01:00
spacotte-vates
4bc614ffd1 fix(xo-server-auth-oidc): fix groups import on string (#9280)
Introduced in 6cd0710

For some OIDC auth provider, when the user only has 1 group, the idToken groups param is a string and not an array.
2025-12-08 15:17:09 +01:00
Thierry Goettelmann
1640a6ae00 feat(web-stack): enhance tree lib and add persistence to XO6 navigation tree (#9277) 2025-12-05 15:56:05 +01:00
Thierry Goettelmann
66b677af28 feat: migrate XO Lite tables. introduces new tables. clean code. (#9265) 2025-12-03 17:31:24 +01:00
Thierry Goettelmann
655bda4789 feat(web-stack): table system iteration (composition over config) (#9247) 2025-12-02 17:57:54 +01:00
Thierry Goettelmann
07e605162a refactor(web-core): merge busy prop into type in VtsStateHero (#9273) 2025-12-02 17:36:29 +01:00
Pierre Donias
ce98c3deba fix(web/vdis): xo 5 fallback URL query should start with a "?" (#9267)
Introduced by #9232
See https://xcp-ng.org/forum/post/100170
2025-12-02 10:24:44 +01:00
MlssFrncJrg
71fa8924d7 feat: release 5.113.1 (#9263) 2025-11-28 13:01:09 +01:00
MlssFrncJrg
9c78018662 feat: technical release (#9262)
* feat(xo-server-perf-alert): 1.0.3

* chore(CHANGELOG): update next
2025-11-28 12:16:37 +01:00
Florent BEAUCHAMP
72f31d412d fix(xo-server-perf-alert): compute core average if cpu_usage is missing (#9258) 2025-11-28 11:51:55 +01:00
MlssFrncJrg
ef60450dc8 feat(lite): 0.17.0 (#9261) 2025-11-28 10:11:57 +01:00
MlssFrncJrg
f4988c00af feat(lite): 0.17.0 (#9260) 2025-11-27 18:06:40 +01:00
MlssFrncJrg
43c9f574fd feat: release 5.113.0 (#9259) 2025-11-27 17:36:38 +01:00
MlssFrncJrg
a15eeeed5b feat: technical release (#9257) 2025-11-27 16:29:04 +01:00
Florent BEAUCHAMP
14a72a2612 fix(xo-server-perf-alert): better handling of VM not loaded in xapi and undefined (#9256) 2025-11-27 16:16:35 +01:00
Mathieu
68554b3e0a fix(@xen-orchestra/core): predicate early return if undefined (#9255)
introduced by 5d28710ab3
2025-11-27 16:05:35 +01:00
MlssFrncJrg
91d5107e1d feat: technical release (#9254)
* feat(@xen-orchestra/web): 0.34.1

* feat(xo-server-perf-alert): 1.0.1

* chore(CHANGELOG): update next
2025-11-27 15:03:51 +01:00
Florent BEAUCHAMP
8e46b83d3e fix(xo-server-perf-alert): copy template to dist and fix imports (#9253) 2025-11-27 14:47:08 +01:00
Mathieu
6105681df2 fix(@xen-orchestra/web): add missing bios_strings field (#9252)
introduced by 0c92bf559a
2025-11-27 14:41:32 +01:00
Thomas Moraine
05713d16c0 docs(proxy.md): explain error message for incompatible proxies (#9245)
Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2025-11-27 13:24:06 +01:00
MlssFrncJrg
1c6bc86794 Release (#9251)
* feat(vhd-lib): 4.14.5

* feat(@vates/nbd-client): 3.2.2

* feat(@xen-orchestra/web-core): 0.35.0

* feat(@xen-orchestra/rest-api): 0.21.1

* feat(@xen-orchestra/vmware-explorer): 0.10.6

* feat(@xen-orchestra/web): 0.34.0

* feat(xo-server): 5.193.1

* feat(xo-server-auth-oidc): 0.4.0

* feat(xo-server-perf-alert): 1.0.0

* feat(xo-web): 5.191.0

* chore(CHANGELOG): update next

* feat(vhd-cli): 1.0.0

* add missing lib
2025-11-27 13:23:09 +01:00
Florent BEAUCHAMP
21b4d1ee34 docs(xo-server): guidelines (#8583)
Co-authored-by: Mathieu <70369997+MathieuRA@users.noreply.github.com>

Co-authored-by: Bastien Nollet <150058367+b-Nollet@users.noreply.github.com>

Co-authored-by: Mathieu <goore.csmoviz@gmail.com>

---------

Co-authored-by: Mathieu <70369997+MathieuRA@users.noreply.github.com>
Co-authored-by: Bastien Nollet <150058367+b-Nollet@users.noreply.github.com>
Co-authored-by: Mathieu <goore.csmoviz@gmail.com>
2025-11-27 11:45:22 +01:00
MlssFrncJrg
0c92bf559a feat(xo6/web): add boot firmware at vm creation (#9158) 2025-11-27 10:51:36 +01:00
Mathieu
5d28710ab3 feat(@xen-orchestra/web): implement reactivity for sub-stores (#9250) 2025-11-27 09:42:29 +01:00
Thierry Goettelmann
3126688d5e fix(web): charts reload every 30 seconds (#8939) 2025-11-27 09:24:51 +01:00
Elise-F
e70fe39934 feat(xo6): implement vdis tab page (#9232) 2025-11-26 18:33:41 +01:00
Enée DI IORIO
0af02941ed feat(xo6/pool/hosts): refactoring pool hosts page (#9218) 2025-11-26 17:54:10 +01:00
Florent BEAUCHAMP
779876ede9 feat(xo-server-perf-alert): improve performances (#9070) 2025-11-26 17:25:55 +01:00
Enée DI IORIO
dd8bb763e5 fix(xo-stack/resourceManagement): fix resourceManagement card if controllerMemory is undefined (#9197) 2025-11-26 17:04:42 +01:00
Enée DI IORIO
5e6c19b39f fix(web-core): add tooltip on UiTag (#9184) 2025-11-26 16:05:40 +01:00
Mathieu
dbb76cce6e fix(@xen-orchestra/rest-api): status code 400 instead of 500 for CM parse error (#9244)
See https://team.vates.fr/vates/pl/uu1c3r4bzbnx7gjon8xf6wmzsc
XO-1569
2025-11-26 15:15:53 +01:00
Mathieu
5cf1de5356 feat(xo6): implement reactivity for XAPI objects (#9183) 2025-11-26 14:20:50 +01:00
OlivierFL
bed27e5ffa fix(xo-6/dashboard): prevent double scrollbar in alarms lists (#9246) 2025-11-26 12:00:42 +01:00
Mathieu
bff4e4d4e2 fix(@xen-orchestra/rest-api/backup-archive): fix cannot convert undefined or null to object (#9240)
introduced by 544590a
fix zammad#48178
2025-11-26 09:47:45 +01:00
Enée DI IORIO
97d202dae0 feat(web-core): update UiLabel from V1 to V3 (#9201) 2025-11-26 09:40:20 +01:00
spacotte-vates
460bbc4569 fix(nbd-client): fix esxi 6 warm migration (#9223) 2025-11-26 09:21:19 +01:00
Bastien Nollet
708089622d feat(vhd-cli): accept all types of remotes (#9235) 2025-11-26 09:02:08 +01:00
OlivierFL
1818a031af feat(xo6/header): add DC Scope and DC NetScope buttons (#9242) 2025-11-26 08:23:56 +01:00
Mathieu
5cc6154bd2 fix(@xen-orchestra/rest-api): remove unused import (#9241) 2025-11-25 16:14:15 +01:00
spacotte-vates
6cd0710f89 feat(xo-server-auth-oidc): import user groups from OIDC (#9206) 2025-11-25 16:11:47 +01:00
Cyrille G.
65858c9ce7 feat(hub/recipes): add support for XO CCM and custom CIDR configurations in Pyrgos form (#9236) 2025-11-25 12:10:27 +01:00
Florent BEAUCHAMP
6277a12c4a fix(nbd-client): nbddisk must emit full size block (#9233) 2025-11-25 11:57:41 +01:00
Thomas Moraine
16de2e9fca docs(configuration.md): document changing the default XO interface (#9225) 2025-11-25 10:56:59 +01:00
Pierre Donias
39fa0f041f feat(xo-web/disk): (dis)connectVbd → (dis)connectVbds (#9211)
See Zammad#47676

Always use bulk methods for connecting or disconnecting a VBD so that
confirmation modals behaviours are mutualized.
2025-11-25 10:51:52 +01:00
vates-bot
ca3a7540f4 feat(web-stack/i18n): update Xen Orchestra 6 (#9165) 2025-11-25 10:40:59 +01:00
MlssFrncJrg
0bac1d3e0e feat: technical release (#9237) 2025-11-24 17:30:39 +01:00
MlssFrncJrg
99f221ccbf feat(xo-6): inform user about VM creation limitations (#9203) 2025-11-24 14:51:52 +01:00
Pierre Brunet
f24952f967 chore(xo-web): update marked to more recent version (#9227)
Fix https://github.com/vatesfr/xen-orchestra/security/dependabot/27
2025-11-24 14:36:08 +01:00
Pierre Brunet
566b210d1e chore(xo-web): update ansi_up (#9226) 2025-11-24 13:55:45 +01:00
Florent BEAUCHAMP
5fc2e4e8e5 fix(nbd-client): wrong comment leads to bad code (#9222) 2025-11-24 10:44:58 +01:00
Mathieu
65de89c7ee fix(@xen-orchestra/rest-api): don't return job for /backup-jobs endpoint (#9210)
* fix(@xen-orchestra/rest-api): don't return job for /backup-jobs endpoint
2025-11-24 10:37:06 +01:00
Bastien Nollet
1c55ca61e2 feat(xo-server-load-balancer): affinity tags (#9116)
Add Affinity rule option to the load balancer plugin.
This options ensures some VMs are positioned on the same host, when it's possible.

It's important to be careful while configuring the Affinity tags and Anti-affinity tags fields, otherwise it may lead to conflicts between the two behaviours. In such case, affinity takes precedence over anti-affinity.
2025-11-24 10:29:29 +01:00
Mathieu
ed5bd4d42e Feat/use vif move for network change (#9221)
* feat(vif): use VIF.move for network changes instead of delete+create
2025-11-24 09:56:00 +01:00
Julien Porschen
488eec9d1c fix(backups): handle LVM on GPT partitions #7029 (#9182)
* fix(backups): handle LVM on GPT partitions on file level restore #7029
* fix(backups): ignore swap partition of file level restore
2025-11-24 09:51:28 +01:00
Elise-F
89d45caac9 feat(xo6): fetch xo gui routes (#9138) 2025-11-24 08:51:44 +01:00
Mathieu
2592f2fc2c feat(xo-server-usage-report): add RAM/CPU allocation to reports (#9224) 2025-11-20 17:28:35 +01:00
spacotte-vates
87547a2130 fix(@xen-orchestra/rest-api): async actions return json (#9209) 2025-11-20 16:12:21 +01:00
Enée DI IORIO
5349f2ad09 fix(web-core): remove secondary cancel button on search input (#9189)
fix(web-core/uiInput): remove secondary cancel button on search input
2025-11-19 17:02:19 +01:00
Joris K
d34025fd25 feat(xo6/host): update vms view and implement new page vms and sidepanel (#9193) 2025-11-19 08:59:56 +01:00
Joris K
c53758ddc5 feat(xo6/pool) update vms view and implement new page vms and sidepanel (#9196) 2025-11-19 08:39:22 +01:00
Elise-F
90d1207a3c feat(xo6): creating setting page (#9175) 2025-11-18 17:16:15 +01:00
Joris K
9f478f97a2 feat(xo6/site): implement vms page (#9145) 2025-11-18 17:05:59 +01:00
Pierre Briffaux
e86bd71819 fix(web-core/MenuList): add overflow scroll to prevent content clipping (#9215) 2025-11-18 17:04:08 +01:00
Pierre Brunet
53c1a33106 security: update nodemailer (#9217)
Fix Nodemailer: Email to an unintended domain can occur due to Interpretation Conflict: https://github.com/vatesfr/xen-orchestra/security/dependabot/246
2025-11-18 09:23:27 +01:00
Pierre Brunet
7c06abc99f chore(saml): update GW tutorial (#9119)
Changes thanks to the forum posts here:
https://xcp-ng.org/forum/topic/6174/saml-auth-with-azure-ad/29?_=1760599896992
2025-11-18 09:06:23 +01:00
Pierre Brunet
2a5b0ffd1a security: update package xml2js in xo-web and xo-vmdk-to-vhd (#9216)
Update xml2js in xo-web and xo-vmdk-to-vhd for security purpose
Fix https://github.com/vatesfr/xen-orchestra/security/dependabot/84
2025-11-18 09:04:54 +01:00
Pierre Brunet
e494007b2a security: update cookie package (#9220)
Update cookie package in xo-server for security
Fix https://github.com/vatesfr/xen-orchestra/security/dependabot/167

Proxy check tested
2025-11-18 09:03:48 +01:00
OlivierFL
c2fb0470fc feat(xo-6/host): add storage repositories page and table (#9137) 2025-11-17 15:46:42 +01:00
OlivierFL
5fb5e639cd feat(xo-6/pool): add storage repositories page (#9134)
* fix reactivity issue in progress.util

* update VtsProgressBar component to display progress bar with no ruler

* create and use ProgressCell in Table
2025-11-17 15:01:42 +01:00
OlivierFL
da3733a3a9 feat(xo-6/backups): update wordings for backups modes (#9199) 2025-11-17 11:10:57 +01:00
Florent BEAUCHAMP
bd7ba64b68 fix(vmware-explorer): force vddk library path (#9208)
fix ticket 47466
2025-11-14 16:29:34 +01:00
Enée DI IORIO
436e2ea168 feat(xo6/site) implement hosts page (#9128) 2025-11-14 16:09:32 +01:00
Mathieu
dcbd550961 fix(@xen-orchestra/rest-api): fix eventListener not reattached (#9191) 2025-11-14 11:56:50 +01:00
Thierry Goettelmann
8811bb4bac feat(xo6): migrate typing to @vates/types (#9186) 2025-11-14 09:09:33 +01:00
Joris K
2d06677887 fix(xo6/host/vm): fix issues with condition on dashboards, and chart translation (#9204) 2025-11-13 09:23:37 +01:00
Mathieu
6cfefc91e4 feat(@xen-orchestra/rest-api): expose vms/:id/dashboard (#9143) 2025-11-12 18:01:41 +01:00
Florent BEAUCHAMP
8dc7254128 fix(backups): getOldEntries must accept entries without id or timestamp (#9202)
introduced by #9180 https://github.com/vatesfr/xen-orchestra/pull/9180
2025-11-12 17:42:18 +01:00
Pierre Donias
18a41cd055 feat(xo-web/menu): add link to XO 6 (#9187) 2025-11-12 15:31:06 +01:00
spacotte-vates
a7ad70bbed feat(rest-api): add vm.tags to backups-archives (#9190) 2025-11-12 11:10:17 +01:00
Mathieu
fa0202f5b3 docs(xo): add Gmail App Password and Microsoft 365 auth guidance (#9194)
* docs(xo): add Gmail App Password and Microsoft 365 auth guidance

* fix(docs): update email authentication guidance and improve clarity

* fix(docs): clarify backup-reports plugin configuration step

Co-authored-by: Thomas Moraine <thomas.moraine@vates.tech>

---------

Co-authored-by: Mathieu Piton <mathieu.piton@vates.tech>
Co-authored-by: Thomas Moraine <thomas.moraine@vates.tech>
2025-11-10 09:32:40 +01:00
Elise-F
88b88187e9 feat(xo6): updated ui of input search (#9156)
* feat(xO6): udpate ui of input search

* updated changelog file

* feat(web-core): updated story of ui input component

* feedbacks on PR
2025-11-07 17:21:02 +01:00
Mathieu
ade825df55 fix(@xen-orchestra/xapi): await VBD.plug (#9192)
introduced by d353dc622c
2025-11-07 15:07:21 +01:00
Thierry Goettelmann
042c9c645b chore(types): complete exiting xo type definitions (#9174) 2025-11-06 10:05:51 +01:00
Dan Pollak
6bf5f4b7d4 feat(xo-server/recover-account): supply password via CLI (#9173)
Allow password to be passed as a parameter
2025-11-05 16:40:41 +01:00
Bastien Nollet
063f499ee0 fix(docs): fix long-term retention doc (#9178) 2025-11-05 15:39:03 +01:00
Mathieu
e4d484e84f feat(xo-server-usage-report): add OS information to usage reports (#9179)
---------

Co-authored-by: Mathieu Piton <mathieu.piton@vates.tech>
2025-11-05 14:24:29 +01:00
Florent BEAUCHAMP
05c6f87a12 fix(backups): long term retention taking the most recent values (#9180) 2025-11-05 11:01:43 +01:00
Pierre Donias
8a390057b6 fix(backups): typos and spelling (#9154)
See #8593

Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@vates.tech>
2025-11-05 10:20:58 +01:00
Alexander Sennhauser
82d65e3c68 describe correct tag for backup health check (#9010) 2025-11-05 10:17:17 +01:00
Pierre Donias
58f029dcbe feat(xo-web/VM/backup): warn that backup job affects all VMs (#9155)
See Zammad#45581
2025-11-04 15:58:32 +01:00
Mathieu
7112c33142 feat(@xen-orchestra/rest-api): expose events (#9130) 2025-11-04 15:05:06 +01:00
Thomas Moraine
08c2923386 docs(xo): document immutability (#9135)
Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2025-11-04 11:50:41 +01:00
Thomas Moraine
ed5be40099 docs(manage_infrastructure.md): update container/docker information (#9140)
This commit renames and updates the **Docker management** section in the Xen Orchestra documentation. We explain a but better how containers are supported in Xen Orchestra, and remove links and references to the now-removed Docker support section, which was triggering a Docusaurus warning at build time.

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2025-11-04 09:40:28 +01:00
Bastien Nollet
8c038bac34 feat: release 5.112.1 (#9177) 2025-11-03 14:37:36 +01:00
Bastien Nollet
961b505cfb feat: technical release (#9176) 2025-11-03 12:14:53 +01:00
Florent BEAUCHAMP
32556029cd feat(backups): export qcow2 disk as qcow2 (#9170)
fix full backups for delta
2025-11-03 10:54:11 +01:00
Mathieu
65013190e3 fix(xo-server/rpu): update linstor-satellite and linstor-controller (#9171) 2025-11-03 10:29:33 +01:00
OlivierFL
2dd451a7d9 fix(xo-6/backup-settings): reactivity issue when using useXoBackupJobSettingsUtils (#9169)
Introduced by e547447
2025-10-31 14:27:16 +01:00
Pierre Brunet
73a8ba59ba fix(xo-web): update easyvirt forms for DC Net Scope (#9107) 2025-10-31 11:07:33 +01:00
Bastien Nollet
285d1da361 feat(lite): 0.16.0 (#9167) 2025-10-30 18:24:47 +01:00
Bastien Nollet
4d77b79ce9 feat: release 5.112.0 (#9166) 2025-10-30 17:55:30 +01:00
Bastien Nollet
268033439b feat: technical release (#9164) 2025-10-30 15:25:41 +01:00
Florent BEAUCHAMP
df3a365eb7 fix(xapi): use qcow2 exports only for bigger disks (#9163) 2025-10-30 15:01:05 +01:00
MlssFrncJrg
61ee81ea3a fix(@vates/types): add packages to release in changelog (#9161)
introduced by cfb64f8
2025-10-30 12:50:37 +01:00
vates-bot
2789b339cb feat(web-stack/i18n): update Xen Orchestra 6 (#9095) 2025-10-30 11:59:16 +01:00
Olivier Floch
c895f05486 feat(xo6): introduce constants.ts file to avoid duplications 2025-10-30 11:17:43 +01:00
Olivier Floch
ed3f50cb1a feat(xo6/backups): add breadcrumb in backups pages header 2025-10-30 11:17:43 +01:00
Olivier Floch
6bb90082b9 feat(xo-core): add UiBreadCrumb component 2025-10-30 11:17:43 +01:00
Florent BEAUCHAMP
c870bb653a fix(backups): use qcow2 for export of qcow2 disks (#9160) 2025-10-30 09:20:35 +01:00
Florent BEAUCHAMP
b11484ce6d fix(xo-web): image format in disk tab of vm (#9157) 2025-10-29 16:04:02 +01:00
MlssFrncJrg
cfb64f8e0d feat(@xen-orchestra/rest-api): add boot firmware for VM creation (#9147) 2025-10-29 15:51:11 +01:00
Enée DI IORIO
d6e0f70510 feat(xo-6/backups) add backup targets page (#9048) 2025-10-29 15:33:56 +01:00
Pierre Donias
b47a7055a2 feat(netbox): support Netbox v4.4.x (#9153)
Fixes #8958
2025-10-29 15:26:19 +01:00
MlssFrncJrg
a101e9e02d feat(@xen-orchestra/rest-api): expose gui routes (#9133) 2025-10-29 15:04:11 +01:00
Enée DI IORIO
e547447bb4 feat(xo-6/backups): add backup configuration page (#9008) 2025-10-29 14:49:44 +01:00
Florent BEAUCHAMP
5d92f813f6 feat(xo-server): support import of disk > 2TB toward qcow2 SR (#9148) 2025-10-29 10:13:44 +01:00
Florent BEAUCHAMP
ab4e20b0a5 fix(vhd-lib): error while handling open error (#9151)
fix an error Cannot read properties of undefined while handling an error raised during a vhd file error.
2025-10-29 10:04:25 +01:00
Mathieu
ff4b087be8 feat(xo-cli): add system status download functionality for hosts and pools (#9141)
Add two new API methods to download XCP-ng/XenServer system status (bug reports) via xo-cli:

    host.getSystemStatus - Download system status from a single host
    pool.getSystemStatuses - Download system status from all hosts in a pool in parallel


---------

Co-authored-by: Mathieu Piton <mathieu.piton@vates.tech>
2025-10-29 09:29:20 +01:00
Elise-F
09208ba0fb feat(xo6): added loader in treeview when search is triggered (#9142)
* feat(xo6): added loader in treeview when search is triggered

* update changelog file

* fix(xo6): code cleanup following PR feedback

* fix order packages in changelog file
2025-10-29 09:23:54 +01:00
Elise-F
896e9386a9 feat(xo6): added new links in the user menu and customized it (#9126)
* feat(xo6): added new links in the user menu and customized it

* udpate Changelog file

* fix(xo6): code cleanup following PR feedback

* fix order packages in changelog file
2025-10-29 09:15:10 +01:00
Olivier Lambert
87471d90c4 feat(@xen-orchestra/xapi): expose IPMI sensors for Lenovo hardware (#9149)
---------

Signed-off-by: Olivier Lambert <olivier.lambert@vates.fr>
Co-authored-by: Mathieu <70369997+MathieuRA@users.noreply.github.com>
Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@vates.tech>
2025-10-28 19:15:37 +01:00
Enée DI IORIO
740fc03a04 feat(xo-6/backups): add backed up vms page (#9018)
Co-authored-by: Olivier Floch <olivier.floch@vates.fr>
2025-10-28 17:08:08 +01:00
Florent BEAUCHAMP
45ef65b251 fix(qcow2): number/bigint comparison (#9150) 2025-10-28 15:47:27 +01:00
Florent BEAUCHAMP
bf705e0a98 feat(xo-web): add merge synchronously option to mirror backups (#9118) 2025-10-28 14:15:30 +01:00
Thomas Moraine
989864314f docs: upgrade docusaurus from version 3.8 to 3.9.2 (#9073)
docs: upgrade docusaurus from version 3.8 to 3.9

The onBrokenMarkdownLinks option is deprecated and has been replaced by siteConfig.hooks.onBrokenMarkdownLinks. However, since the option was configured to 'warn' and is the default behavior for this option, deleting the onBrokenMarkdownLinks seems like easier

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2025-10-28 12:20:53 +01:00
MlssFrncJrg
0a377f9568 feat(@xen-orchestra/rest-api): expose ping (#9129) 2025-10-28 10:10:27 +01:00
Anthoine Bourgeois
cb96de6d11 feat: Qcow2Disk: support sub-cluster feature
Signed-off-by: Anthoine Bourgeois <anthoine.bourgeois@vates.tech>
2025-10-28 09:43:37 +01:00
Anthoine Bourgeois
7e032e9f84 fix: Qcow2Disk: fix host cluster offset mask
Signed-off-by: Anthoine Bourgeois <anthoine.bourgeois@vates.tech>
2025-10-28 09:43:37 +01:00
Bastien Nollet
51855c22d4 feat: technical release (#9146) 2025-10-27 12:13:55 +01:00
MathieuRA
676b059485 fix(xo-lite): set default cores-per-socket value 2025-10-27 11:41:38 +01:00
MathieuRA
81d0d28819 fix(xo-server): set default cores-per-socket value 2025-10-27 11:41:38 +01:00
Pierre Brunet
9123921676 Chore: update rimraf packages to fix glob ending support (#9046)
feat: update rimraf
2025-10-27 09:48:17 +01:00
Mathieu
544590a860 feat(@xen-orchestra/rest-api): expose backup-archives (#8982)
XO-1468
2025-10-24 17:29:10 +02:00
OlivierFL
63e7eb881c feat(xo-6/backups): add backup runs page (#9007) 2025-10-24 11:09:35 +02:00
Bastien Nollet
030d697392 fix(xo-server-load-balancer): correctly avoid migrating tagged VMs (#9139)
Introduced by 9ef05b8afe
2025-10-23 15:45:32 +02:00
Florent BEAUCHAMP
af56933587 fix(backups): wrong error on close before init (#9125)
this happens if the disk init fails, for example when trying
to read qcow2 disk from a non qcow2 SR
2025-10-22 15:56:21 +02:00
bleader
ec0bdb3286 docs(sdn-controller): document OpenFlow XAPI plugin usage (#9114)
- State to use a single XOA instance with SDN Controller plugin enabled
- Cleanup existing OpenFlow documentation
- Document XAPI plugin usage, including xo-cli commands
- Add configuration to toggle between direct channel and XAPI plugin
2025-10-21 16:46:14 +02:00
OlivierFL
70055960d0 feat(xo-core): update logical properties wording in CSS guidelines (#9132) 2025-10-21 11:12:52 +02:00
Pierre Donias
a9ab882b75 fix(fatfs): typos and spelling (#9131)
See #8593
2025-10-20 16:49:04 +02:00
Elise-F
bce7408f1f feat(xo6): add loader on input search treeview when is triggered (#9122)
* feat(xo6): add loader on input search treeview when is triggered

* udpate Changelog file

* fix(xo6): removed condition on props icon in UiInput

* fix(xo6): removed debounced on clear
2025-10-20 15:20:12 +02:00
Mathieu
c8abbb259e fix(@xen-orchestra/rest-api): add bad request response documentation (#9108) 2025-10-20 14:07:38 +02:00
Thomas Moraine
35cc755bf0 docs(xo): specify configuration file path (#9127)
Several sections of the Xen Orchestra Appliance documentation mention modifying the config file, without specifying the actual file path. This commit updates those references to explicitly include /etc/xo-server/config.toml, which helps users locate and edit the correct configuration file more easily.

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2025-10-20 12:10:09 +02:00
Bastien Nollet
5750b58362 fix(xo-web/Size): fix saving Size component (#9121)
Changing the value of a `Size` component by editing the field and pressing
'Enter' was executing twice the `_save()` function in parallel (which calls the
`onChange` function).

In some cases like editing a running VM memory limit, this caused errors (trying
to open two modals at the same time, and trying to restart the VM twice)
2025-10-20 10:28:28 +02:00
Joris K
8a95941297 fix(xo6): fix formatted date in backup jobs (#9124) 2025-10-20 08:45:17 +02:00
Florent BEAUCHAMP
8289150460 feat(vmware-explore): add more debug when failing to get thumbprint (#9012)
from ticket 45117
2025-10-17 14:25:44 +02:00
Florent BEAUCHAMP
87df162944 chores(all): remove jest (#8956)
jest test are unmaintained and flacky, they increase the dependencies surface
2025-10-17 14:24:05 +02:00
Thierry Goettelmann
e9eda42d7d feat(web-stack): new VtsStatus component (#9052) 2025-10-17 10:14:59 +02:00
Thierry Goettelmann
d942d144ca feat(web-core): add objectIcon helper (#9053) 2025-10-17 09:40:50 +02:00
Elise-F
d829816f62 fix(xo6): display user menu above tree structure (#9115)
* fix(xo6): display user menu above tree structure

* add CHANGELOG
2025-10-16 16:47:01 +02:00
Florent BEAUCHAMP
8b3b23e995 fix(backups): throw an error on non unique target for replication (#9117)
from ticket 40151
2025-10-16 15:49:00 +02:00
Cécile Morange
52807ed15f docs(xo): change link to the xcp-ng docs, add some infos in HA section (#9112)
* docs(xo): change link to the xcp-ng docs, add some infos in HA section

* Update docs/docs/manage_infrastructure.md

Co-authored-by: Thomas Moraine <thomas.moraine@vates.tech>

* Update docs/docs/manage_infrastructure.md

Co-authored-by: Thomas Moraine <thomas.moraine@vates.tech>

* Update docs/docs/manage_infrastructure.md

Co-authored-by: Thomas Moraine <thomas.moraine@vates.tech>

---------

Co-authored-by: Thomas Moraine <thomas.moraine@vates.tech>
2025-10-16 14:20:58 +02:00
Florent BEAUCHAMP
596f9ac536 fix(vhd-lib): better error handling
ensure disk is really closed, and that error are caught
2025-10-16 11:30:25 +02:00
Florent BEAUCHAMP
d58efc7b2d fix(xapi): implement Throttling on Xapi/Remote read 2025-10-16 11:30:25 +02:00
Florent BEAUCHAMP
73aa66a219 fix(xapi): implement Timeout on Xapi read 2025-10-16 11:30:25 +02:00
Florent BEAUCHAMP
93c8903953 fix(backups): put back rate speed to full backup, replication and mirror 2025-10-16 11:30:25 +02:00
Florent BEAUCHAMP
dc8c455a0b feat(generator-toolbox): handle unlimited throttle (speed at 0) 2025-10-16 11:30:25 +02:00
Thierry Goettelmann
d601558bf4 feat(web-core): new table system (#9054) 2025-10-16 10:39:22 +02:00
Mathieu
d5cee13e25 fix(@xen-orchestra/rest-api): use middleware instead of route for users/me redirection (#9103)
Introduced by #8985
2025-10-16 09:41:36 +02:00
Florent BEAUCHAMP
151d7ed22a fix(immutable-backups): double delete that can block immut lifting (#9104) 2025-10-15 17:39:11 +02:00
OlivierFL
3d29348337 feat(xo-6/collections): implement virtual lists for Treeview, QuickTasks and Alarms collections in dashboards (#9077) 2025-10-15 17:17:32 +02:00
Thomas Moraine
62ab5a2f5b docs(xo): remove mentions of xscontainer from the xo doc (#9113)
docs(manage_infrastructure.md): remove content related to xscontainer, which is deprecated

Since XSContainer is no longer supported on XCP-ng 8.3 or XenServer and
we currently have no alternative, instructions related to XScontainer in
the XO doc are removed

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2025-10-15 14:33:31 +02:00
Pierre Brunet
c2d1c66a59 fix(plugin/auth-saml): add default config in SAML (#9093) 2025-10-14 16:30:58 +02:00
Bastien Nollet
730d0ff41d fix(xo-web/Toggle): swallow undefined errors thrown by confirm (#8953)
Updating the `Toggle` component to avoid throwing an error coming from a
`confirm` modal, which ends up appearing in the console.

This also changes a bit the behaviour of the `Toggle` component, which now
awaits its `onChange` function.
2025-10-14 15:11:18 +02:00
Mathieu
64d164300b feat(@xen-orchestra/rest-api): expose pbds (#9106) 2025-10-14 14:44:26 +02:00
Mathieu
04eeb78df7 fix(@vates/types): fix XoSr allocationStrategy (#9105) 2025-10-14 10:44:33 +02:00
Mathieu
6bdfbfb953 feat(@xen-orchestra/rest-api): expose users/authentication_tokens (#9102) 2025-10-14 09:48:31 +02:00
Mathieu
e6573e26a9 feat(xo-server/rest-api): remove legacy code (#9101) 2025-10-14 09:41:28 +02:00
Mathieu
5f5cb9b45d feat(@xen-orchestra/rest-api): expose vdis/:id.(vhd|raw) (#9038)
* feat(@xen-orchestra/rest-api): expose vdi(s|-snapshots)/:id.(vhd|raw)
2025-10-10 14:39:31 +02:00
MlssFrncJrg
940e879a1c feat(@xen-orchestra/rest-api): expose vm-controllers/:id/tags/:tag (#9097) 2025-10-10 11:36:11 +02:00
MlssFrncJrg
861567d0db feat(@xen-orchestra/rest-api): expose vm-templates/:id/tags/:tag (#9099) 2025-10-10 11:23:57 +02:00
MlssFrncJrg
8e536a4c32 feat(@xen-orchestra/rest-api): expose vm-snapshots/:id/tags/:tag (#9098) 2025-10-10 11:10:08 +02:00
MlssFrncJrg
e100736349 feat(@xen-orchestra/rest-api): expose srs/:id/tags/:tag (#9089) 2025-10-10 11:03:17 +02:00
MlssFrncJrg
98e4bcccfa feat(@xen-orchestra/rest-api): expose vdis/:id/tags/:tag (#9094) 2025-10-10 10:43:35 +02:00
MlssFrncJrg
938b34343d feat(@xen-orchestra/rest-api): expose vdi-snapshots/:id/tags/:tag (#9091) 2025-10-10 10:31:03 +02:00
MlssFrncJrg
e1ecc2d6d4 feat(@xen-orchestra/rest-api): remove vifs/:id/tags/:tag (#9096) 2025-10-10 10:22:12 +02:00
Pierre Donias
afabf35c66 fix(xo-web/Copiable): remove gap in hover box (#9059)
See Zammad#45442

Wrap "Copy to clipboard" button with a span to make the hoverable zone
continuous between the copiable element and the button. Without this, the 1ex
gap between the 2 was sometimes enough to make the button disappear before the
user had a chance to click it.
2025-10-10 10:19:50 +02:00
MlssFrncJrg
8e0f48bddd feat(@xen-orchestra/rest-api): remove vbds/:id/tags/:tag (#9090) 2025-10-10 10:13:34 +02:00
MlssFrncJrg
70dc82e621 feat(@xen-orchestra/rest-api): expose srs/:id/tasks (#9086) 2025-10-10 10:03:18 +02:00
Mathieu
2928813613 feat(@xen-orchestra/rest-api): expose vms/:id/tags/:tag (#9092) 2025-10-10 09:54:57 +02:00
MlssFrncJrg
a86d07adea feat(@xen-orchestra/rest-api): expose vbds/:id/tasks (#9085) 2025-10-10 09:48:00 +02:00
Mathieu
a282e10069 feat(@xen-orchestra/rest-api): expose pools/:id/tasks (#9080) 2025-10-09 16:39:13 +02:00
MlssFrncJrg
8a70196fa9 feat(@xen-orchestra/rest-api): expose pools/:id/tags/:tag (#9088) 2025-10-09 14:40:04 +02:00
MlssFrncJrg
1d8ae5eea8 feat(@xen-orchestra/rest-api): expose networks/:id/tags/:tag (#9087) 2025-10-09 12:54:03 +02:00
Mathieu
055bce7d91 feat(@xen-orchestra/rest-api): expose hosts/:id/tags/:tag (#9037) 2025-10-09 10:47:54 +02:00
Thomas Moraine
1ee07eb06f docs(xo): document object storage support list (#9068)
Adds a page to the Xen Orchestra documentation to list the compatible S3-based
storage support providers for Xen orchestra, with the different support tiers
and appropriate caveats.
2025-10-09 09:53:50 +02:00
Joris K
6a5eb12051 fix(xo6/site): issue in backups tab (#9064)
* fix(xo6/site): issue in backups tab

* add changelog
2025-10-08 17:50:05 +02:00
Pierre Brunet
fbf3f1bc7f chore: update doc for SAML due to breaking changes in config (#9084) 2025-10-08 17:13:31 +02:00
MlssFrncJrg
dedfa91669 feat(@xen-orchestra/rest-api): expose vdis-snapshots/:id/tasks (#9082) 2025-10-08 15:47:16 +02:00
MlssFrncJrg
f3480f6939 feat(@xen-orchestra/rest-api): expose vdis/:id/tasks (#9079) 2025-10-08 15:38:35 +02:00
Mathieu
e48166846c feat(@xen-orchestra/rest-api): expose vbds/:id/messages (#9029) 2025-10-08 15:23:05 +02:00
Mathieu
f7a79a0be3 feat(@xen-orchestra/rest-api): expose hosts/:id/tasks (#9074) 2025-10-08 15:02:42 +02:00
Mathieu
f0f1fd1715 feat(@xen-orchestra/rest-api): expose networks/:id/tasks (#9076) 2025-10-08 14:56:47 +02:00
MlssFrncJrg
2e4c45b207 fix(xo-server/rest-api): fix indentation (#9081) 2025-10-08 14:49:44 +02:00
Mathieu
72a3f0a9da feat(@xen-orchestra/rest-api): expose pifs/:id/tasks (#9078) 2025-10-08 14:45:05 +02:00
MlssFrncJrg
2d6f784f0a feat(@xen-orchestra/rest-api): expose vifs/:id/tasks (#9075) 2025-10-08 14:19:48 +02:00
MlssFrncJrg
614449d540 feat(@xen-orchestra/rest-api): migrate vm-controllers tasks (#9069) 2025-10-08 14:12:57 +02:00
Thierry Goettelmann
53ff333778 feat(web-core): introducing toComputed helper (+ migrate codebase) (#8960) 2025-10-08 11:49:12 +02:00
Thierry Goettelmann
4207954982 feat(web-stack): migrate modal system to web-core + enhancements + new UiModal (#8887) 2025-10-08 11:48:46 +02:00
Mathieu
6af64c7095 feat(@xen-orchestra/rest-api): expose groups/:id/tasks (#9072)
XO-1187
2025-10-08 11:05:28 +02:00
Mathieu
7d03560a3e feat(@xen-orchestra/rest-api): expose users/:id/tasks (#9066)
XO-1188
2025-10-08 10:26:06 +02:00
Julien Fontanet
9f9519ef15 docs(xoa): use HTTP for alternative install (#9071)
Introduced by 51b06b1177

Fixes #9067
2025-10-07 20:29:29 +02:00
MlssFrncJrg
0838b112de feat(@xen-orchestra/rest-api): migrate vm-templates tasks (#9004) 2025-10-07 17:29:48 +02:00
Pierre Donias
d6e4456538 fix(xo-web/VM/ActionBar): do not hide actions if user is admin (#9061)
See Zammad#44366

Admin users should always be able to perform actions on VMs even if the VM was
created with Self Service.
2025-10-07 15:57:06 +02:00
Mathieu
022dc0bf2e feat(@xen-orchestra/rest-api): expose servers/:id/tasks (#9065) 2025-10-07 14:55:51 +02:00
MlssFrncJrg
49909df1d2 feat(@xen-orchestra/rest-api): migrate vm-snapshots tasks (#9005) 2025-10-07 14:35:05 +02:00
Mathieu
7c7d7a938e feat(@xen-orchestra/rest-api): expose vm-controllers/:id/messages (#9050) 2025-10-07 14:16:14 +02:00
Mathieu
a67ad48012 feat(@xen-orchestra/rest-api): expose vifs/:id/messages (#9049) 2025-10-06 16:29:27 +02:00
Mathieu
6c6d7a377f feat(@xen-orchestra/rest-api): expose vdis/:id/messages (#9044) 2025-10-06 16:24:11 +02:00
Mathieu
d3006026ca feat(@xen-orchestra/rest-api): expose vdi-snapshots/:id/messages (#9043) 2025-10-06 16:04:41 +02:00
Mathieu
62cdd56691 feat(@xen-orchestra/rest-api): expose networks/:id/messages (#9023) 2025-10-06 15:58:25 +02:00
Mathieu
775e8a8b12 feat(@xen-orchestra/rest-api): expose pifs/:id/messages (#9021) 2025-10-06 15:49:44 +02:00
Mathieu
f5676705cc fix(@xen-orchestra/rest-api): move getTasksObject out of a service (#9058)
Introduced #8955
2025-10-06 15:40:29 +02:00
Pierre Donias
0bad96418b feat: release 5.111.1 (#9062) 2025-10-06 15:26:01 +02:00
Pierre Donias
3222025088 feat: technical release (#9060) 2025-10-06 14:15:47 +02:00
Pierre Brunet
86f3096c1b chore: update packages (#9042)
- passport-saml -> @node-saml/passport-saml
- connect-flash -> typed-flash
2025-10-06 10:42:44 +02:00
Florent BEAUCHAMP
d7fe4f1729 fix(vmware-explorer): thumbprint is case insensitive (#9045)
* fix(vmware-explorer): thumbprint is case insensitive

depending on version ? it can answer a text starting woth sha1 or SHA1
2025-10-03 13:47:27 +02:00
Florent BEAUCHAMP
64a2666645 fix(asyncEach): handle iterator error
this was the cause of  xo:backups:worker WARN possibly unhandled rejection
and the backup error code not triggerring on timeout and various
edge cases
2025-10-03 11:19:55 +02:00
Florent BEAUCHAMP
3b7f623f9e fix(fs): retry writing a file should not throw EEXIST on previous failed write
also removing the retry on outputfile, since outputfile call _writeFile which
is already retried
2025-10-03 11:19:55 +02:00
Florent BEAUCHAMP
16cdbc16fa fix(v2v): do not lock stopped VM (#9047)
imported VM are locked to prepare for a future resume
stopped VM are completly transferred and won't need a future resume
2025-10-03 10:23:26 +02:00
Mathieu
52510ed03c fix(xo-server/users): name property was not present in getAllUsers (#9019)
* fix(xo-server/users): name property was not present in getAllUsers

* Update packages/xo-server/src/xo-mixins/subjects.mjs

Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@vates.tech>

---------

Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@vates.tech>
2025-10-03 10:05:27 +02:00
Mathieu
bbec7d4532 feat(@xen-orchestra/rest-api): expose pools/:id/messages (#9022)
* feat(@xen-orchestra/rest-api): expose pools/:id/messages
2025-10-02 15:13:45 +02:00
Mathieu
cf0449c034 fix(@xen-orchestra/rest-api): fix messages tag (#9024) 2025-09-30 17:00:38 +02:00
Mathieu
9e93ba14ae feat(@xen-orchestra/rest-api): expose hosts/:id/messages (#9027) 2025-09-30 17:00:28 +02:00
Mathieu
bf330311a1 feat(@xen-orchestra/rest-api): expose srs/:id/messages (#9028) 2025-09-30 16:53:56 +02:00
Pierre Donias
481ef5e585 feat(lite): 0.15.0 (#9036) 2025-09-30 16:21:33 +02:00
Pierre Donias
a5debf3241 feat: release 5.111.0 (#9035) 2025-09-30 16:07:25 +02:00
Pierre Donias
d8c106fc5d feat: technical release (#9034) 2025-09-30 15:46:07 +02:00
Enée DI IORIO
8d2af27896 fix(xo-lite/settings): fix key-value alignment (#8981)
* feat(xo-lite/settings): fix key-value alignment

Refactor page for use of more regular components and match better to Figma

* review

* review
2025-09-30 15:40:21 +02:00
Joris K
893cdcbc86 feat(xo6): add alarms in host and vm dashboard, add patches in host dashboard (#8937)
* feat(xo6): add alarms in host and vm dashboard, add patches in host dashboard

* feedback
2025-09-30 15:00:33 +02:00
Pierre Donias
241c37b35c feat: technical release (#9033) 2025-09-30 13:54:47 +02:00
Thierry Goettelmann
57e1b94115 fix(web): failing build (#9030)
Introduced by #8966
2025-09-30 13:25:52 +02:00
Thierry Goettelmann
7c670b479e fix(web): unable to change tab in tasks quick panel (#8930)
* fix(web): unable to change tab in quick task panel

* update CHANGELOG.unreleased.md
2025-09-30 12:33:02 +02:00
Pierre Donias
a1103f6da9 feat: technical release (#9026)
* feat(@xen-orchestra/web-core): 0.31.0

* feat(@xen-orchestra/rest-api): 0.17.0

* feat(@xen-orchestra/web): 0.29.0

* feat(xo-server): 5.190.0

* feat(xo-server-sdn-controller): 1.2.0

* feat(xo-web): 5.187.0

* chore(CHANGELOG): update next
2025-09-30 09:52:59 +02:00
Pierre Brunet
4c5b5f603b feature(hub/recipes): add RGPD compliance in EasyVirt form and bootAfterCreate option (#9016)
Add RGPD compliance in easyvirt creation form and bootAfterCreate option
2025-09-30 09:08:46 +02:00
Pierre Brunet
d77d6d0d9f fix(new-vm): prevent error in case of empty usersshkeys (#9015)
Introduced by #8968 
Reported in #9014
2025-09-29 11:11:05 +02:00
Mathieu
93be967459 feat(@xen-orchestra/xapi): expose vm-templates messages (#8995) 2025-09-29 10:22:59 +02:00
Florent BEAUCHAMP
a62ac1ab82 feat(sdn-controller): use host plugin (#8488)
* feat(sdn-controller): use XCP plugin to manage open switch

try to instantiate the plugin and fall back to direct query
if it fails
The plugin allow to define and target multiple bridges in parallel

* feat(sdn-controller): implement network wide rules

this introduce 2 new API method sdnController.addNetworkRule
and sdnController.deleteNetworkRule
Theses methods will only work with the plugin, and can't work with
the legacy ( direct channel) approach
2025-09-26 17:46:45 +02:00
OlivierFL
55f51184c2 feat(xo-6/vm): add side panel in VM backup jobs page (#8978) 2025-09-26 17:30:37 +02:00
OlivierFL
f7ae461337 feat(xo-6/backups): add backup jobs side panel (#8966) 2025-09-26 17:21:42 +02:00
vates-bot
3f57f2b8ec feat(web-stack/i18n): update Xen Orchestra 6 (#8901) 2025-09-26 13:50:23 +02:00
Pierre Donias
2786c9c392 feat(xo-web/pool): dc scope/netscope button (access or deploy) (#8951)
Try to detect a DC Scope and/or DC NetScope VM and add buttons to access the DC
Scope/NetScope instances.
2025-09-26 10:20:04 +02:00
Joris K
332b4d6197 fix(xo6/vm): fix issue due to incorrect value naming (#8998) 2025-09-26 09:39:58 +02:00
Lux_
8fa7a77fab feat(xo-core/i18n): update french translations (#8983) 2025-09-26 09:34:45 +02:00
Pierre Donias
a0046fa19f feat: technical release (#9013) 2025-09-25 16:15:05 +02:00
Pierre Brunet
692f60c323 feat(xo-web/recipes): add recipe to create EasyVirt VMs (DC Scope and DC NetScope) (#8797)
See xoa#244
2025-09-25 14:45:04 +02:00
Florent BEAUCHAMP
9e4fe7a2a3 fix(xo-web/xostor): resource without volumes should be shown (diskless/tiebreaker) (#8944)
Some tie breaker and diskless volume can be useful for support and advanced usages
2025-09-25 14:09:43 +02:00
Pierre Donias
bcee503a77 chore(CHANGELOG): update LDAP security fix entry (#9011)
See #8639
2025-09-25 11:01:56 +02:00
Mathieu
29af04b8f2 fix(sdn-controller/addHost): fix no PIF found in center (#9000) 2025-09-24 15:19:43 +02:00
Mathieu
c00659ba61 fix(@vates/types): fix typo in warning (#9006) 2025-09-24 12:15:34 +02:00
OlivierFL
d7afda4c52 feat(xo-6/treeview): add debounce to search input (#8992) 2025-09-23 17:05:27 +02:00
Mathieu
d07732119b feat(@xen-orchestra/rest-api): expose vm-snapshots/:id/messages (#8997) 2025-09-23 16:25:35 +02:00
MlssFrncJrg
396ba3868f feat(@xen-orchestra/rest-api): migrate vms tasks (#8955) 2025-09-23 16:15:05 +02:00
MlssFrncJrg
4453bbc16c feat(@xen-orchestra/rest-api): expose users authentication_tokens (#8865) 2025-09-23 15:51:09 +02:00
Mathieu
b01adbe2e1 feat(@xen-orchestra/rest-api): expose (backup|restore)-logs (#8987) 2025-09-23 14:30:42 +02:00
Thomas Moraine
46f5e7bc0d docs(xo): add backup strategy guide (#8881)
Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@vates.tech>
2025-09-23 14:08:19 +02:00
Thomas Moraine
d76e7744e2 docs(xo): document the new migration process (#8934)
Since the 5.110 release of Xen Orchestra, the Vmware to Vates (V2V) process has been updated, with a whole new backend. This commit documents this new process, in the XO documentation

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2025-09-22 16:52:49 +02:00
Mathieu
b6100980fc feat(@xen-orchestra/rest-api): expose vms/:id/messages (#8988) 2025-09-22 16:23:24 +02:00
Pierre Donias
b8a839068e fix(xo-web/getLicenseNearExpiration): properly check if trial is running (#8991)
Pass the license object to isTrialRunning instead of passing the parent object.

See Zammad#44878 Zammad#44755 Zammad#44431 Zammad#43526 Zammad#44556 ...
Introduced by #8557
See #8523 → #8551 → #8557
2025-09-22 16:22:01 +02:00
Pierre Donias
d2fd68df74 feat(xo-web/PIFs): natural sort by device (#8967) 2025-09-22 09:55:33 +02:00
Mathieu
5d03dc3c10 fix(@xen-orchestra/backups): fix exportIncrementalVm VTPMs (#8990) 2025-09-22 09:55:13 +02:00
Bastien Nollet
1c01f1c7fe fix(xo-server-load-balancer): prevent mass migration when receiving incorrect CPU data (#8965) 2025-09-19 19:20:30 +02:00
Enée DI IORIO
8135148217 feat(xo-lite/settings): add icon for external link (#8980) 2025-09-19 11:02:08 +02:00
Mathieu
067ee26715 feat(@xen-orchestra/rest-api): expose POST srs/:id/vdis (#8984) 2025-09-19 10:13:19 +02:00
Mathieu
c8827ab076 feat(@xen-orchestra/rest-api): expose /users/me* (#8985) 2025-09-19 09:40:30 +02:00
Mathieu
ba4b9b6cb3 feat(@xen-orchestra/rest-api): expose backup-jobs (#8970)
XO-643
2025-09-17 15:19:25 +02:00
Pierre Donias
44d93b90b3 fix(xo-server/resource-sets): only edit limits if limits is passed (#8979)
See https://xcp-ng.org/forum/topic/11241/why-changing-objects-in-resource-set-wipe-limits
Introduced by #7353
2025-09-17 14:02:58 +02:00
Pierre Brunet
7bb6592be4 feat(xo-web/new-vm): add sshkeys in cloudconfig (#8968) 2025-09-17 10:10:20 +02:00
Julien Fontanet
8f2b8791cc feat(complex-matcher): 1.0.0 2025-09-16 14:57:12 +02:00
Julien Fontanet
b56ffbdb8d feat(complex-matcher/Comparison): can match with non numbers
It will use implicit conversion.
2025-09-16 14:53:20 +02:00
Julien Fontanet
b4e05b3183 chore(complex-matcher): add tests for Comparison 2025-09-16 14:52:54 +02:00
Julien Fontanet
c2c8d1b1ea chore(complex-matcher): move number check to Comparison constructor 2025-09-16 14:52:54 +02:00
Joris K
857d038316 feat(xo6/vm): add backup jobs view (#8976) 2025-09-15 17:15:42 +02:00
Joris K
b16d568b7a feat(xo6/lite): update vtsStateHero component (#8910) 2025-09-15 14:33:37 +02:00
Florent BEAUCHAMP
1471ab0c7c fix(backups): replication doing always a full (#8971)
introduced by 8935
sr.$VDIs are xen api objects not xo objects
2025-09-15 14:29:37 +02:00
Mathieu
ee74a640b4 feat(@xen-orchestra/rest-api): expose delete vdi*/:id (#8961) 2025-09-15 14:16:40 +02:00
Mathieu
44bfc28a44 fix(@vates/types,@xen-orchestra/rest-api): fix eslint config (#8974) 2025-09-15 12:25:31 +02:00
Joris K
de0c535976 feat(xo6/lite): update quick info in dashboard VM to match figma (#8952) 2025-09-15 11:05:57 +02:00
Joris K
3fdc97b690 fix(CHANGELOG): add missing packages (#8975)
introduced by ec042d1
2025-09-15 10:05:12 +02:00
Joris K
ec042d1089 feat(web-core): update ui collapsible list component (#8954) 2025-09-12 16:41:54 +02:00
Florent BEAUCHAMP
a459015ca9 Fix(replication): VDI_NOT_MANAGED error (#8935)
from ticket #40151
2025-09-11 15:42:34 +02:00
Florent BEAUCHAMP
4944ea902f fix(schedule): mark sequence progress as completly done on success (#8527) 2025-09-10 17:49:56 +02:00
Mathieu
4c93609bfe feat(xo-server/rpu): add more XO task (#8950) 2025-09-10 17:24:30 +02:00
Bastien Nollet
c2144947c9 feat(xo-server): add a backup guard to reclaiming space on SR (#8947) 2025-09-10 11:06:36 +02:00
Pierre Donias
0d481ffb18 chore: update dependencies (#8888) 2025-09-09 16:48:09 +02:00
Mathieu
b4c22f623d feat(@xen-orchestra/rest-api): expose vms/:id/backup-jobs (#8948)
* feat(@xen-orchestra/rest-api): expose vms/:id/backup-jobs
2025-09-09 15:10:20 +02:00
Pierre Donias
e84e34efa5 fix(xo-web/pool-item): handle undefined license info in render() (#8946)
See Zammad#44148
Introduced by #6655
2025-09-09 15:06:21 +02:00
Mathieu
878e8ceea7 feat(@xen-orchestra/rest-api): expose users/:id/groups (#8936) 2025-09-09 14:58:07 +02:00
Mathieu
876c28d392 feat(@xen-orchestra/rest-api): expose groups/:id/users (#8932) 2025-09-09 14:19:13 +02:00
Bastien Nollet
6a08b82d86 chore(packages): remove unused xo-server-test (#8945)
remove unused xo-server-test
2025-09-09 10:18:06 +02:00
Mathieu
9dda9be6f0 feat(@xen-orchestra/rest-api): expose delete /vms*/:id (#8938) 2025-09-08 17:22:53 +02:00
Pierre Brunet
98ddd115fd feat(xo-web): change vif device cast to number (#8877) 2025-09-08 17:14:54 +02:00
Mathieu
39cb465941 feat(@xen-orchestra/rest-api): expose (vms/vm-snapshots/vm-templates)/:id.(xva|ova) (#8929) 2025-09-08 16:51:48 +02:00
Mathieu
f28181b07c feat(@xen-orchestra/rest-api): expose tasks/:id/actions/abort (#8908) 2025-09-08 16:42:37 +02:00
Mathieu
31bbf0bdff feat(@xen-orchestra/rest-api): expose (vdis|vdi-snapshots)/:id.(raw|vhd) (#8923) 2025-09-08 12:28:13 +02:00
Mathieu
a509a7c6b7 feat(@xen-orchestra/rest-api): expose delete tasks and tasks/:id (#8905) 2025-09-08 12:19:52 +02:00
Bastien Nollet
02b732b9da fix(xo-web/pool/advanced): only suggest SRs from pool when enabling HA (#8924)
The modal to activate HA used to suggest SRs from all pools, but selecting a SR from another pool would result to an error. This change makes that modal only suggest SRs from the correct pool.
2025-09-08 11:16:39 +02:00
Florent BEAUCHAMP
89e4f051fc feat(xo-web/home): add copiable id on templates (#8949) 2025-09-08 11:01:49 +02:00
Bastien Nollet
d36d64380d feat(lite): 0.14.1 (#8943) 2025-09-04 16:02:07 +02:00
Bastien Nollet
1dc2d2b6fa feat: release 5.110.1 (#8941) 2025-09-04 14:55:50 +02:00
Bastien Nollet
348dfab021 feat: technical release (#8940)
* feat(@xen-orchestra/xapi): 8.4.1

* feat(@xen-orchestra/backups): 0.64.2

* feat(@xen-orchestra/proxy): 0.29.30

* feat(@xen-orchestra/vmware-explorer): 0.10.2

* feat(@xen-orchestra/web): 0.27.1

* feat(xo-server): 5.188.0

* feat(xo-web): 5.185.0

* chore(CHANGELOG): update next
2025-09-04 09:59:47 +02:00
Bastien Nollet
fbc5e3e071 fix(@xen-orchestra/xapi): better fallback messages for backups (#8926)
better fallback messages for backups. 
This will give the user more information on mixed delta/base backups disk per disk
2025-09-04 09:28:11 +02:00
Florent BEAUCHAMP
f7feaaa4f9 feat(xo-server): Make timeout for import xva from url configurable (#8931)
Make timeout for import xva from url configurable

the entry in the config file is

[jsonrpc-api]
xvaImportFromUrlTimeout = '6s'
2025-09-04 09:11:35 +02:00
Florent BEAUCHAMP
a96865198d feat(xo-server/v2v): UI fixes (#8927) 2025-09-03 17:31:59 +02:00
Florent BEAUCHAMP
114df4e18e fix(xo-server/v2v): transfer resume retransfer the full VM (#8928)
current code was looking for a snapshot, but the import should write
on the VM, and its disk
2025-09-03 16:02:45 +02:00
Mathieu
3676ce9764 fix(CHANGELOG): fix changelog URL (#8933) 2025-09-03 10:33:53 +02:00
Thomas Moraine
3512a2f768 docs(xo): document recipes (#8919)
Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2025-09-03 09:55:56 +02:00
Dan Pollak
d68c8c7ee0 fix(v2v): messages typos (#8914) 2025-09-02 14:02:06 +02:00
Mathieu
0c2c948712 fix(xo-lite/vm/system): correctly display secure boot (#8925) 2025-09-02 13:53:15 +02:00
Joris K
c9d053e70d fix(xo6): display only ISO VDIs for the ISO input list in the VM creation form (#8922) 2025-09-02 13:45:49 +02:00
Mathieu
c3905bc10d feat(@xen-orchestra/rest-api): expose proxies (#8920) 2025-09-01 14:51:38 +02:00
Thierry Goettelmann
ba30ca08d9 fix(web): typo host dashboard ram provisioning legend (#8915) 2025-09-01 10:00:45 +02:00
Bastien Nollet
4dfd4829ad feat(lite): 0.14.0 (#8918) 2025-08-28 16:33:57 +02:00
Bastien Nollet
e0fccacc2f feat(lite): 0.14.0 (#8917) 2025-08-28 16:10:41 +02:00
Bastien Nollet
3937eadbce feat: release 5.110.0 (#8916) 2025-08-28 15:34:56 +02:00
Bastien Nollet
043387a913 feat: technical release (#8913) 2025-08-28 11:41:28 +02:00
Pierre Brunet
88f2e3b7eb fix(backup): set transfer tag only on AbstractRemote (#8907)
* fix(backup): add transfer tag in FullRemote
* fix: move tag where it is really useful
2025-08-28 11:03:34 +02:00
Florent BEAUCHAMP
ea6410e711 fix(backup) error footer1 !== footer2 (#8882)
* fix(vhd-lib): remove unused parent locator
* fix(vhd-lig): improve error messages one wrong end footer
2025-08-28 10:58:27 +02:00
Florent BEAUCHAMP
e1ce9eca7b fix(v2v): improve vddk import (#8911)
* fix(nbd-client): better handling of error during getMap
* fix(xo-server): esxi check errors are objects instead of text
* fix(xo-web): V2v/check improve alignment
* feat(xo-web): add button to install nbdinfo/nbdkit if needed
2025-08-28 10:46:54 +02:00
Thierry Goettelmann
b0cfd9e7f9 feat(web-stack): VtsProgressBar + VtsProgressBarGroup + migration (#8899) 2025-08-28 10:16:39 +02:00
OlivierFL
4f44af6763 fix(xo-6/backup-jobs): correctly display "pending" jobs (#8909) 2025-08-28 09:41:53 +02:00
Bastien Nollet
384bb37dd4 feat: technical release (#8906) 2025-08-27 11:54:12 +02:00
Mathieu
a4410a3c82 fix(@xen-orchestra/rest-api): fix watch tasks (#8900) 2025-08-27 10:18:22 +02:00
Florent BEAUCHAMP
e900f61ed6 fix(vmware-explorer): ignore incomplete vm 2025-08-26 18:36:13 +02:00
Florent BEAUCHAMP
a65f6cd42d feat(xo-server): implement vddk + v2V 2025-08-26 18:36:13 +02:00
Florent BEAUCHAMP
aee55234ea feat(xo-server): expose api to check vddk install 2025-08-26 18:36:13 +02:00
Florent BEAUCHAMP
5ddb78fdf6 task(read-chunk): improve error message and type 2025-08-26 18:36:13 +02:00
Florent BEAUCHAMP
f8cb07596b feat(nbd-client): implement a Disk adapter based on NBD connection 2025-08-26 18:36:13 +02:00
Florent BEAUCHAMP
6c32155327 feat(nbd-client): implement getMap based on nbdinfos 2025-08-26 18:36:13 +02:00
Thierry Goettelmann
c6d8ccf85a feat(web-stack): migration all icons to new icon library (#8736) 2025-08-26 17:51:05 +02:00
Pierre Brunet
659e636657 fix(fs): update timeout for b2 listing (#8903) 2025-08-26 17:28:08 +02:00
Pierre Brunet
d15776e44a fix(backups): prevent Health check error about tags if no transfer during backups (#8879)
add flag to prevent checking for tags if no transfer was executed in incremental backups
2025-08-26 17:06:48 +02:00
Bastien Nollet
e9ba0bd494 feat(xo-server): implement unregisterRestApi (#8898) 2025-08-26 16:41:55 +02:00
OlivierFL
f1e3e6968a feat(xo-6/account-menu): add link to REST API documentation (#8904) 2025-08-26 16:38:27 +02:00
Florent BEAUCHAMP
354284f6ca feat(xo-web): add link to rest api doc (#8902) 2025-08-26 15:16:50 +02:00
Pierre Brunet
f53b78bef1 feat(@xen-orchestra/rest-api): add docs/swagger.json route (#8892) 2025-08-26 14:57:42 +02:00
OlivierFL
769dc19baa feat(xo-6/site): add backup jobs view (#8889) 2025-08-26 14:41:46 +02:00
vates-bot
9d3cffd211 feat(web-stack/i18n): update Xen Orchestra 6 (#8837) 2025-08-26 10:01:50 +02:00
Mathieu
c1c3b56441 feat(@xen-orchestra/rest-api): expose host missing_patches (#8862) 2025-08-25 17:44:23 +02:00
Thierry Goettelmann
e0748796eb feat(web-core): new useProgress and useProgressGroup composables (#8897) 2025-08-25 17:15:13 +02:00
Thierry Goettelmann
4ac481e311 feat(web-core): new useThreshold composable (#8896) 2025-08-25 16:47:33 +02:00
Thierry Goettelmann
05847ca1fc feat(lite): add i18n engage link on settings page (#8884) 2025-08-25 16:31:21 +02:00
OlivierFL
dd7538b82b feat(xo-core): update UiTablePagination component (#8891) 2025-08-25 15:37:45 +02:00
Bastien Nollet
b665bb3e6e fix(xo-server): don't end sequence on skipped backup (#8859) 2025-08-25 15:01:53 +02:00
Mathieu
a324da30bc feat(@xen-orchestra/rest-api): expose pools/:id/missing_patches (#8871) 2025-08-25 14:45:25 +02:00
Mathieu
5911d266f9 feat(@xen-orchestra/xapi): expose IPMI sensors for Dell hardware (#8861) 2025-08-25 14:42:52 +02:00
Thomas Moraine
2090898ce2 docs(xo): explain causes of large incremental backups (#8860)
Added a new section to the Incremental backups documentation to explain why some deltas can be unexpectedly large. Covers common causes and includes tips for investigation and mitigation.

This helps clarify expected behavior and avoids confusion when backup sizes seem too large.

add tips for 2tb+ disks and qcow disks

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>

---------

Signed-off-by: Thomas Moraine <thomas.moraine@vates.tech>
2025-08-25 11:47:07 +02:00
Mathieu
5d66e6de5f feat(@xen-orchestra/rest-api): expose xo tasks (#8843) 2025-08-25 09:17:00 +02:00
2469 changed files with 164455 additions and 53205 deletions

View File

@@ -233,7 +233,29 @@ module.exports = {
'@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error',
'@intlify/vue-i18n/no-missing-keys-in-other-locales': [
'error',
{ ignoreLocales: ['de', 'fa', 'cs', 'es', 'sv', 'it', 'ru', 'uk', 'nl'] },
{
ignoreLocales: [
'de',
'fa',
'cs',
'es',
'sv',
'it',
'ru',
'uk',
'nl',
'pt-BR',
'da',
'ko',
'nb-NO',
'ja',
'fi',
'pl',
'zh-Hans',
'sk',
'pt',
],
},
],
},
},
@@ -259,7 +281,7 @@ module.exports = {
},
},
{
files: ['@xen-orchestra/{web-core,lite,web}/typed-router.d.ts'],
files: ['@xen-orchestra/{web-core,lite,web}/src/route-map.d.ts'],
parserOptions: {
sourceType: 'module',
},
@@ -268,6 +290,17 @@ module.exports = {
'eslint-comments/no-unlimited-disable': 'off',
},
},
{
files: ['*.d.ts'],
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
},
rules: {
'n/no-unsupported-features/es-syntax': 'off',
'no-unused-vars': 'off',
},
},
],
ignorePatterns: ['@vates/fatfs/'],

View File

@@ -25,7 +25,7 @@ jobs:
- name: Setup Node environment
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '>=22.12'
cache: 'yarn'
- name: Install project dependencies

View File

@@ -3,4 +3,5 @@
**/*.hbs
# Generated files
/@xen-orchestra/web/typed-router.d.ts
/@xen-orchestra/web/src/route-map.d.ts
/@xen-orchestra/lite/src/route-map.d.ts

5
@vates/async-each/index.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
export function asyncEach<Item>(
iterable: Iterable<Item> | AsyncIterable<Item>,
iteratee: (item: Item, index: number, iterable: Iterable<Item> | AsyncIterable<Item>) => Promise<void>,
opts?: { concurrency?: number; signal?: AbortSignal; stopOnError?: boolean }
): Promise<void>

View File

@@ -73,20 +73,20 @@ exports.asyncEach = function asyncEach(iterable, iteratee, { concurrency = 10, s
}
nextIsRunning = true
if (running < concurrency) {
const cursor = await it.next()
if (cursor.done) {
next = () => {
if (running === 0) {
if (errors.length === 0) {
resolve()
} else {
reject(new AggregateError(errors))
try {
const cursor = await it.next()
if (cursor.done) {
next = () => {
if (running === 0) {
if (errors.length === 0) {
resolve()
} else {
reject(new AggregateError(errors))
}
}
}
}
} else {
++running
try {
} else {
++running
const result = iteratee.call(this, cursor.value, index++, iterable)
let then
if (result != null && typeof result === 'object' && typeof (then = result.then) === 'function') {
@@ -94,9 +94,9 @@ exports.asyncEach = function asyncEach(iterable, iteratee, { concurrency = 10, s
} else {
onFulfilled(result)
}
} catch (error) {
onRejected(error)
}
} catch (error) {
onRejected(error)
}
nextIsRunning = false
return next()

View File

@@ -67,20 +67,17 @@ describe('asyncEach', () => {
})
it('stops on first error when stopOnError is true', async () => {
const tracker = new assert.CallTracker()
const error = new Error()
const iteratee = tracker.calls((_, i) => {
const iteratee = spy((_, i) => {
if (i === 1) {
throw error
}
}, 2)
})
assert.deepStrictEqual(
await rejectionOf(asyncEach(iterable, iteratee, { concurrency: 1, stopOnError: true })),
error
)
tracker.verify()
assert.deepStrictEqual(iteratee.callCount, 2)
})
it('rejects AggregateError when stopOnError is false', async () => {
@@ -100,19 +97,30 @@ describe('asyncEach', () => {
})
it('can be interrupted with an AbortSignal', async () => {
const tracker = new assert.CallTracker()
const ac = new AbortController()
const iteratee = tracker.calls((_, i) => {
const iteratee = spy((_, i) => {
if (i === 1) {
ac.abort()
}
}, 2)
})
await assert.rejects(asyncEach(iterable, iteratee, { concurrency: 1, signal: ac.signal }), {
message: 'asyncEach aborted',
})
assert.deepStrictEqual(iteratee.callCount, 2)
})
tracker.verify()
it('handle error on iterable', async () => {
const iteratee = spy(async () => {})
const iterable = async function* () {
for (const value of [1, 2, 3]) {
if (value === 2) {
throw new Error('iterator error')
}
await randomDelay()
yield value
}
}
await assert.rejects(() => asyncEach(iterable(), iteratee, { concurrency: 1 }), { message: 'iterator error' })
})
})
)

View File

@@ -24,7 +24,8 @@
"url": "https://vates.fr"
},
"license": "ISC",
"version": "1.0.0",
"types": "index.d.ts",
"version": "1.0.3",
"engines": {
"node": ">=8.10"
},

View File

@@ -24,8 +24,8 @@
},
"dependencies": {
"@vates/multi-key-map": "^0.2.0",
"@xen-orchestra/async-map": "^0.1.2",
"@xen-orchestra/log": "^0.7.1",
"@xen-orchestra/async-map": "^0.1.3",
"@xen-orchestra/log": "^0.7.2",
"ensure-array": "^1.0.0"
},
"devDependencies": {

View File

@@ -62,7 +62,7 @@ exports.parseFlags = function (flags) {
case 'ax': info = {read:false, write:true, create:true, append:true, exclusive:true}; break;
case 'a+': info = {read:true, write:true, create:true, append:true}; break;
case 'ax+': info = {read:true, write:true, create:true, append:true, exclusive:true}; break;
default: throw Error("Uknown mode: "+flags); // TODO: throw as `S.err.INVAL`
default: throw Error("Unknown mode: "+flags); // TODO: throw as `S.err.INVAL`
}
if (info.sync) throw Error("Mode not implemented."); // TODO: what would this require of us?
if (_dir) info._openDir = true;

View File

@@ -1,7 +1,7 @@
{
"private": false,
"name": "@vates/fatfs",
"version": "0.11.0",
"version": "0.11.1",
"description": "fs implementation on top of raw FAT16/FAT32 block source",
"main": "index.js",
"scripts": {

View File

@@ -90,7 +90,7 @@ function startTests(vol, waitTime) {
assert(path === file, "Fixed fluffy path matches normal one.");
});
fs.realpath([BASE_DIR,"non","existent","path"].join('/'), function (e) {
assert(e, "Expected error calling fs.realpath on non-existent file.");
assert(e, "Expected error calling fs.realpath on nonexistent file.");
});
fs.readdir(BASE_DIR, function (e, arr) {

View File

@@ -1,12 +1,12 @@
import LRU from 'lru-cache'
import Fuse from 'fuse-native'
import LRU from 'lru-cache'
import { createLogger } from '@xen-orchestra/log'
import { VhdSynthetic } from 'vhd-lib'
import { Disposable, fromCallback } from 'promise-toolbox'
const { warn } = createLogger('vates:fuse-vhd')
// build a s stat object from https://github.com/fuse-friends/fuse-native/blob/master/test/fixtures/stat.js
// build a stat object from https://github.com/fuse-friends/fuse-native/blob/master/test/fixtures/stat.js
const stat = st => ({
mtime: st.mtime || new Date(),
atime: st.atime || new Date(),
@@ -20,42 +20,109 @@ const stat = st => ({
export const mount = Disposable.factory(async function* mount(handler, diskPath, mountDir) {
const vhd = yield VhdSynthetic.fromVhdChain(handler, diskPath)
const cache = new LRU({
max: 16, // each cached block is 2MB in size
})
await vhd.readBlockAllocationTable()
const { blockSize } = vhd.header
// A zeroed buffer returned for VHD blocks that are not present in the chain.
// Never mutated — safe to share across all reads.
const EMPTY_BLOCK = Buffer.alloc(blockSize, 0)
// --- coalescing block read queue ---
//
// FUSE read requests from NTFS-3g arrive in small chunks (typically 4 KiB)
// while VHD blocks are 2 MiB. Reading blocks one at a time from the backing
// store (CIFS/NFS/…) bounds libuv threadpool usage to 1 concurrent I/O call.
//
// Coalescing: all pending requests for the same blockId share a single
// readBlock() call. When the block resolves, every waiter is fulfilled at once.
//
// Together these two properties prevent the FUSE-on-FUSE threadpool deadlock:
// concurrent NTFS-3g reads no longer starve the VHD block reads they depend on.
// In-memory block cache. Each VHD block is 2 MiB; 16 blocks = 32 MiB.
// This is the primary tool against the 300× CIFS amplification: NTFS-3g
// reads a 2 MiB block in ~512 sequential 4 KiB FUSE requests. Without a
// cache, each request causes a full 2 MiB CIFS read. With the cache, only
// the first request reads from CIFS; the rest are served from RAM.
const blockCache = new LRU({ max: 64 })
const pendingBlocks = new Map() // blockId → Promise<Buffer>
const readQueue = [] // { blockId, resolve, reject }[]
let queueRunning = false
// log stats every 10 s while there is activity
function enqueueBlock(blockId) {
// 1. serve from cache if available
if (blockCache.has(blockId)) {
return Promise.resolve(blockCache.get(blockId))
}
// 2. coalesce: join an existing in-flight read for this block
if (pendingBlocks.has(blockId)) {
return pendingBlocks.get(blockId)
}
const p = new Promise((resolve, reject) => {
readQueue.push({ blockId, resolve, reject })
})
pendingBlocks.set(blockId, p)
p.then(
data => {
pendingBlocks.delete(blockId)
blockCache.set(blockId, data)
},
() => pendingBlocks.delete(blockId)
)
drainQueue()
return p
}
function drainQueue() {
if (queueRunning) return
queueRunning = true
// async IIFE — errors are forwarded to individual reject() callbacks,
// so the outer floating promise is always fulfilled
;(async () => {
while (readQueue.length > 0) {
const { blockId, resolve, reject } = readQueue.shift()
try {
resolve((await vhd.readBlock(blockId)).data)
} catch (err) {
reject(err)
}
}
queueRunning = false
})()
}
async function readData(buf, len, pos) {
if (len === 0) return 0
const startBlockId = Math.floor(pos / blockSize)
// use (pos + len - 1) so an exact block boundary doesn't include the next block
const endBlockId = Math.floor((pos + len - 1) / blockSize)
let copied = 0
for (let blockId = startBlockId; blockId <= endBlockId; blockId++) {
const data = vhd.containsBlock(blockId) ? await enqueueBlock(blockId) : EMPTY_BLOCK
const offsetStart = blockId === startBlockId ? pos % blockSize : 0
const offsetEnd = blockId === endBlockId ? ((pos + len - 1) % blockSize) + 1 : blockSize
data.copy(buf, copied, offsetStart, offsetEnd)
copied += offsetEnd - offsetStart
}
return copied
}
const fuse = new Fuse(mountDir, {
async readdir(path, cb) {
if (path === '/') {
return cb(null, ['vhd0'])
}
if (path === '/') return cb(null, ['vhd0'])
cb(Fuse.ENOENT)
},
async getattr(path, cb) {
if (path === '/') {
return cb(
null,
stat({
mode: 'dir',
size: 4096,
})
)
}
if (path === '/vhd0') {
return cb(
null,
stat({
mode: 'file',
size: vhd.footer.currentSize,
})
)
}
if (path === '/') return cb(null, stat({ mode: 'dir', size: 4096 }))
if (path === '/vhd0') return cb(null, stat({ mode: 'file', size: vhd.footer.currentSize }))
cb(Fuse.ENOENT)
},
read(path, fd, buf, len, pos, cb) {
if (path === '/vhd0') {
return vhd.readRawData(pos, len, cache, buf).then(cb, error => {
return readData(buf, len, pos).then(cb, error => {
warn('read error', { path, len, pos, error })
cb(Fuse.EIO)
})
@@ -64,7 +131,9 @@ export const mount = Disposable.factory(async function* mount(handler, diskPath,
},
})
return new Disposable(
() => fromCallback(cb => fuse.unmount(cb)),
() => {
return fromCallback(cb => fuse.unmount(cb))
},
fromCallback(cb => fuse.mount(cb))
)
})

View File

@@ -1,6 +1,6 @@
{
"name": "@vates/fuse-vhd",
"version": "2.1.2",
"version": "2.1.3",
"license": "ISC",
"private": false,
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/fuse-vhd",
@@ -19,12 +19,12 @@
},
"main": "./index.mjs",
"dependencies": {
"@xen-orchestra/fs": "^4.6.2",
"@xen-orchestra/log": "^0.7.1",
"@xen-orchestra/fs": "^4.9.3",
"@xen-orchestra/log": "^0.7.2",
"fuse-native": "^2.2.6",
"lru-cache": "^7.14.0",
"promise-toolbox": "^0.21.0",
"vhd-lib": "^4.14.0"
"vhd-lib": "^4.16.1"
},
"bin": {
"xo-fuse-vhd": "./cli.mjs"

View File

@@ -1,16 +1,16 @@
{
"name": "@vates/generator-toolbox",
"version": "1.0.4",
"version": "1.1.3",
"main": "dist/index.mjs",
"license": "MIT",
"private": false,
"type": "module",
"devDependencies": {
"@eslint/js": "^9.19.0",
"rimraf": "^5.0.1",
"rimraf": "^6.0.1",
"@types/node": "^20.6",
"typescript": "~5.6",
"typescript-eslint": "^8.23.0"
"typescript-eslint": "^8.61.0"
},
"scripts": {
"build": "tsc",

View File

@@ -144,14 +144,7 @@ class Forked<T, TReturn, TNext> implements AsyncGenerator<T, TReturn, TNext> {
return this.#parent.remove(this.#uid, e)
}
async *[Symbol.asyncIterator](): AsyncGenerator<T> {
while (true) {
const res = await this.next()
if (res.done) {
break
}
yield res.value
}
[Symbol.asyncIterator](): AsyncGenerator<T, TReturn, TNext> {
return this
}
}

View File

@@ -2,8 +2,7 @@ import assert from 'node:assert'
import { suite, test } from 'node:test'
import { Synchronized } from './synchronized.mjs'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function* makeRangeGenerator(end = Infinity, progress = { yielded: 0 }, onYielded = (val: unknown) => {}) {
async function* makeRangeGenerator(end = Infinity, progress = { yielded: 0 }, onYielded = (_val: unknown) => {}) {
for (let i = 0; i < end; i++) {
await new Promise(resolve => setTimeout(resolve, 10))
yield i
@@ -17,7 +16,7 @@ async function consume(
iterable: AsyncGenerator,
delay = 2500,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onConsumed = (val: unknown, iterable: AsyncGenerator) => Promise.resolve(false)
onConsumed = (_val: unknown, _iterable: AsyncGenerator) => Promise.resolve(false)
) {
for await (const val of iterable) {
await new Promise(resolve => setTimeout(resolve, delay))
@@ -28,6 +27,30 @@ async function consume(
}
suite('success', () => {
test('for-await-of break stops the source generator', async () => {
let sourceClosed = false
const source = (async function* () {
try {
for (let i = 0; i < 10; i++) {
await new Promise(resolve => setTimeout(resolve, 10))
yield i
}
} finally {
sourceClosed = true
}
})()
const forker = new Synchronized(source)
const fork = forker.fork('first')
// for-await-of must call fork.return() on break, which propagates to the source
for await (const val of fork) {
if (val === 2) break
}
assert.strictEqual(sourceClosed, true)
})
test('if works with multiple consumer', async () => {
const progress = { yielded: 0 }
const generator = makeRangeGenerator(3, progress)

View File

@@ -19,7 +19,7 @@ export class Throttle {
} else {
speed = this.#bytesPerSecond
}
assert.ok(speed > 0, `speed must be greater than zero, ${speed} computed`)
assert.ok(speed >= 0, `speed must be positive, ${speed} computed`)
return speed
}
constructor(speed: number | (() => number)) {
@@ -29,7 +29,9 @@ export class Throttle {
getNextSlot(length: number): { timeout?: ReturnType<typeof setTimeout>; promise?: Promise<unknown> } {
assert.notStrictEqual(length, undefined, `throttled stream need to expose a length property }`)
assert.ok(length > 0, `throttled stream must expose a positive length property , ${length} given }`)
if (this.speed === 0) {
return {}
}
const previous = this.#previousSlot
const nextSlot = Math.round(previous + (length * 1000) / this.speed)
if (nextSlot < Date.now()) {

View File

@@ -1,4 +1,7 @@
import assert from 'node:assert'
const TIMEOUT = Symbol('timeout')
export class Timeout<T> implements AsyncGenerator {
#source: AsyncGenerator<T>
#timeout: number
@@ -8,21 +11,30 @@ export class Timeout<T> implements AsyncGenerator {
this.#timeout = timeout
}
async next(): Promise<IteratorResult<T>> {
let timeout: ReturnType<typeof setTimeout>
const promiseTimeout = new Promise((_, reject) => {
timeout = setTimeout(() => {
reject(new Error('Timeout reached '))
}, this.#timeout)
})
const promiseNext = new Promise((resolve, reject) => {
this.#source.next().then(res => {
// ensure timetout won't fire later
clearTimeout(timeout)
resolve(res)
}, reject)
})
// promiseTimeout will never resolve
return Promise.race([promiseNext, promiseTimeout]) as Promise<IteratorResult<T>>
let timeoutHandle: ReturnType<typeof setTimeout>
const sourceNext = this.#source.next()
const result = await Promise.race([
sourceNext.then(res => {
clearTimeout(timeoutHandle)
return res
}),
new Promise<typeof TIMEOUT>(resolve => {
timeoutHandle = setTimeout(() => resolve(TIMEOUT), this.#timeout)
}),
])
if (result === TIMEOUT) {
// stop the source once the in-flight next settles to avoid data loss on the next call
// we don't want to await for the end of timeouted code
sourceNext.then(
() => this.#source.return(undefined).catch(() => {}),
() => {} // source already errored, nothing to clean up
)
throw new Error('Timeout reached')
}
return result
}
return(): Promise<IteratorResult<T>> {
return this.#source.return(undefined)

View File

@@ -64,4 +64,24 @@ describe('Timeout class', () => {
await assert.rejects(timeout.throw(new Error('Test error')), 'Expected error was not thrown')
})
it('should close the source generator after a timeout', async () => {
let sourceClosed = false
const slowSource = (async function* () {
try {
// resolves after 100ms so the in-flight next() eventually settles
await new Promise(resolve => setTimeout(resolve, 100))
yield 1
} finally {
sourceClosed = true
}
})()
const timeout = new Timeout(slowSource, 10) // fires before source yields
await assert.rejects(() => timeout.next(), /Timeout reached/)
// wait for the in-flight next() to settle so source.return() can be called
await new Promise(resolve => setTimeout(resolve, 200))
assert.strictEqual(sourceClosed, true)
})
})

View File

@@ -2,6 +2,7 @@
"compilerOptions": {
"target": "es2023",
"module": "ESNext",
"types": ["node"], // Only use types from Node.js and packages that are explicitly imported
"moduleResolution": "bundler",
"strict": true,
"declaration": true, // Generates `.d.ts` files for type safety

View File

@@ -0,0 +1,65 @@
### `create([opts], [requestListener])`
Creates a new server instance. `opts` may contain `createServer` and `createSecureServer` factories (defaults to `http.createServer` and `https.createServer`). `requestListener` is registered as a `request` event listener.
```js
import { create } from '@vates/http-server-plus'
const server = create(function onRequest(req, res) {
res.end('Hello')
})
```
### `server.listen(opts)`
Starts listening. Returns a promise that resolves to a human-readable address string, or rejects with `error.niceAddress` set.
```js
// HTTP on a specific port
await server.listen({ port: 8080 })
// HTTPS — detected automatically when cert+key are provided
await server.listen({
port: 443,
cert: fs.readFileSync('certificate.pem'),
key: fs.readFileSync('private_key.pem'),
})
// Unix socket
await server.listen({ socket: '/var/run/http.sock' })
// File descriptor
await server.listen({ fd: 3 })
// Systemd socket activation
await server.listen({ systemdSocket: 0 })
```
> As a convenience, if `hostname` is `'localhost'`, it will listen on both `127.0.0.1` and `::1`, similar to what Node does when no hostname is provided.
Multiple endpoints can be started on the same instance:
```js
const [httpAddr, httpsAddr] = await Promise.all([server.listen({ port: 80 }), server.listen({ port: 443, cert, key })])
console.log('listening on', httpAddr, httpsAddr)
```
### HTTP/2
Pass a custom `createSecureServer` factory to enable HTTP/2:
```js
import { createSecureServer } from 'node:http2'
import { create } from '@vates/http-server-plus'
const server = create({ createSecureServer }, app)
await server.listen({ port: 443, cert, key })
```
### `server.close()`
Closes all underlying servers. Returns a promise that resolves once the `close` event is emitted.
```js
await server.close()
```

View File

@@ -0,0 +1,98 @@
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
# @vates/http-server-plus
[![Package Version](https://badgen.net/npm/v/@vates/http-server-plus)](https://npmjs.org/package/@vates/http-server-plus) ![License](https://badgen.net/npm/license/@vates/http-server-plus) [![PackagePhobia](https://badgen.net/bundlephobia/minzip/@vates/http-server-plus)](https://bundlephobia.com/result?p=@vates/http-server-plus) [![Node compatibility](https://badgen.net/npm/node/@vates/http-server-plus)](https://npmjs.org/package/@vates/http-server-plus)
> Augmented &#x60;http.Server&#x60;, HTTP/HTTPS/HTTP2 and multiple ports on the same instance
## Install
Installation of the [npm package](https://npmjs.org/package/@vates/http-server-plus):
```sh
npm install --save @vates/http-server-plus
```
## Usage
### `create([opts], [requestListener])`
Creates a new server instance. `opts` may contain `createServer` and `createSecureServer` factories (defaults to `http.createServer` and `https.createServer`). `requestListener` is registered as a `request` event listener.
```js
import { create } from '@vates/http-server-plus'
const server = create(function onRequest(req, res) {
res.end('Hello')
})
```
### `server.listen(opts)`
Starts listening. Returns a promise that resolves to a human-readable address string, or rejects with `error.niceAddress` set.
```js
// HTTP on a specific port
await server.listen({ port: 8080 })
// HTTPS — detected automatically when cert+key are provided
await server.listen({
port: 443,
cert: fs.readFileSync('certificate.pem'),
key: fs.readFileSync('private_key.pem'),
})
// Unix socket
await server.listen({ socket: '/var/run/http.sock' })
// File descriptor
await server.listen({ fd: 3 })
// Systemd socket activation
await server.listen({ systemdSocket: 0 })
```
> As a convenience, if `hostname` is `'localhost'`, it will listen on both `127.0.0.1` and `::1`, similar to what Node does when no hostname is provided.
Multiple endpoints can be started on the same instance:
```js
const [httpAddr, httpsAddr] = await Promise.all([server.listen({ port: 80 }), server.listen({ port: 443, cert, key })])
console.log('listening on', httpAddr, httpsAddr)
```
### HTTP/2
Pass a custom `createSecureServer` factory to enable HTTP/2:
```js
import { createSecureServer } from 'node:http2'
import { create } from '@vates/http-server-plus'
const server = create({ createSecureServer }, app)
await server.listen({ port: 443, cert, key })
```
### `server.close()`
Closes all underlying servers. Returns a promise that resolves once the `close` event is emitted.
```js
await server.close()
```
## Contributions
Contributions are _very_ welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
you've encountered;
- fork and create a pull request.
## License
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)

View File

@@ -0,0 +1,268 @@
'use strict'
// ===================================================================
const EventEmitter = require('events').EventEmitter
const formatUrl = require('url').format
const fromEvent = require('promise-toolbox/fromEvent')
const inherits = require('util').inherits
const resolvePath = require('path').resolve
// ===================================================================
const forwardedEvents = [
'checkContinue',
'checkExpectation',
'clientError',
'connect',
'connection',
'listening',
'request',
'secureConnection',
'upgrade',
]
function extractProperty(obj, prop) {
const value = obj[prop]
if (value !== undefined) {
delete obj[prop]
return value
}
}
function forOwn(obj, cb) {
const keys = Object.keys(obj)
for (let i = 0, n = keys.length; i < n; ++i) {
cb(obj[keys[i]])
}
}
function getSystemdFd(index) {
if (process.pid !== +process.env.LISTEN_PID) {
throw new Error('systemd sockets are not meant for us (LISTEN_PID)')
}
const listenFds = process.env.LISTEN_FDS
if (listenFds == null) {
throw new Error('no systemd sockets found (LISTEN_FDS)')
}
const max = listenFds - 1
if (index > max) {
throw new Error('no such systemd socket found')
}
return index + 3 // 3 = SD_LISTEN_FDS_START
}
const isEmpty = obj => Object.keys(obj).length === 0
// ===================================================================
function onNewListener(event, listener) {
if (forwardedEvents.includes(event)) {
forOwn(this._servers, server => {
server.on(event, listener)
})
}
}
function onRemoveListener(event, listener) {
if (forwardedEvents.includes(event)) {
forOwn(this._servers, server => {
server.off(event, listener)
})
}
}
function Server(opts) {
EventEmitter.call(this)
this._createServer = (opts != null && opts.createServer) || require('http').createServer
this._createSecureServer = (opts != null && opts.createSecureServer) || require('https').createServer
this._servers = Object.create(null)
this._niceAddresses = Object.create(null)
this.on('removeListener', onRemoveListener).on('newListener', onNewListener)
}
inherits(Server, EventEmitter)
const proto = Server.prototype
function getAddress(serverId) {
return this[serverId].address()
}
proto.addresses = function Server$addresses() {
const servers = this._servers
return Object.keys(servers).map(getAddress, servers)
}
function getNiceAddress(serverId) {
return this[serverId]
}
proto.niceAddresses = function Server$niceAddresses() {
const niceAddresses = this._niceAddresses
return Object.keys(niceAddresses).map(getNiceAddress, niceAddresses)
}
function close(server) {
server.close()
}
proto.close = function Server$close(callback) {
if (callback) {
this.once('close', callback)
}
// Emit the close event even if there are no registered servers.
if (isEmpty(this._servers)) {
const self = this
setImmediate(function () {
self.emit('close')
})
return Promise.resolve()
}
// Closes each servers.
forOwn(this._servers, close)
return fromEvent(this, 'close')
}
let nextId = 0
proto.listen = function Server$listen(opts) {
opts = Object.assign({}, opts)
if (opts.hostname === 'localhost') {
return Promise.all([
this.listen(Object.assign(opts, { hostname: '::1' })),
this.listen(Object.assign(opts, { hostname: '127.0.0.1' })),
]).then(addresses => addresses[0])
}
const hostname = extractProperty(opts, 'hostname')
let port = extractProperty(opts, 'port')
const systemdSocket = extractProperty(opts, 'systemdSocket')
let fd = extractProperty(opts, 'fd')
let socket = extractProperty(opts, 'socket')
const servers = this._servers
const niceAddresses = this._niceAddresses
let server, protocol
if (opts.pfx || opts.SNICallback || (opts.cert && opts.key)) {
server = this._createSecureServer(opts)
protocol = 'https'
} else {
server = this._createServer(opts)
protocol = 'http'
}
const id = nextId++
servers[id] = server
if (systemdSocket != null) {
fd = getSystemdFd(systemdSocket)
}
// Compute a temporary nice address to display in case of error.
let niceAddress
if (fd != null) {
server.listen({ fd })
niceAddress = protocol + '://<fd:' + fd + '>'
} else if (socket != null) {
socket = resolvePath(socket)
server.listen(socket)
niceAddress = protocol + '://' + socket
} else {
if (port == null) {
port = protocol === 'https' ? 443 : 80
}
server.listen(port, hostname)
niceAddress = formatUrl({
protocol,
// Hostname default to localhost.
hostname: hostname || 'localhost',
// No port means random, unknown for now.
port: port || '<unknown>',
})
}
const emit = this.emit.bind(this)
server.once('close', function onClose() {
delete servers[id]
delete niceAddresses[id]
if (isEmpty(servers)) {
emit('close')
}
})
server.on('error', function onError() {
delete servers[id]
// FIXME: Should it be forwarded and be fatal if there are no
// listeners?
})
forwardedEvents.forEach(event => {
this.listeners(event).forEach(listener => {
server.on(event, listener)
})
})
return fromEvent(server, 'listening').then(
function () {
const address = server.address()
const niceAddress =
typeof address === 'string'
? protocol + '://' + address
: formatUrl({
protocol,
hostname: address.address,
port: address.port,
})
niceAddresses[id] = niceAddress
return niceAddress
},
function (error) {
error.niceAddress = niceAddress
throw error
}
)
}
function ref(server) {
server.ref()
}
proto.ref = function Server$ref() {
forOwn(this._servers, ref)
}
function unref(server) {
server.unref()
}
proto.unref = function Server$unref() {
forOwn(this._servers, unref)
}
// ===================================================================
module.exports = exports = Server
exports.create = function create(opts, requestListener) {
if (typeof opts === 'function') {
requestListener = opts
opts = undefined
}
const server = new Server(opts)
if (requestListener !== undefined) {
server.on('request', requestListener)
}
return server
}

View File

@@ -0,0 +1,303 @@
'use strict'
// ===================================================================
const assert = require('assert/strict')
const { EventEmitter } = require('events')
const resolvePath = require('path').resolve
const { describe, it } = require('node:test')
const HttpServerPlus = require('./')
// ===================================================================
// Creates a minimal mock server that can be used as a drop-in for http/https.Server.
// `address` is the value returned by mock.address() — use an object for TCP, a string for sockets.
function createMock(address) {
const mock = new EventEmitter()
mock.listen = function () {
setImmediate(() => this.emit('listening'))
}
mock.address = () => address
mock.close = function () {
setImmediate(() => this.emit('close'))
}
return mock
}
// ===================================================================
describe('HttpServerPlus', function () {
describe('#create()', function () {
it('creates a new instance', function () {
assert(HttpServerPlus.create() instanceof HttpServerPlus)
})
it('can register a `request` listener', function () {
const listener = function () {}
const server = HttpServerPlus.create(listener)
assert.deepEqual(server.listeners('request'), [listener])
})
})
describe('.addresses()', function () {
it('returns the list of addresses the server is listening at', function () {
const addr = { address: '127.0.0.1', port: 8080, family: 'IPv4' }
const mock = createMock(addr)
const server = HttpServerPlus.create({ createServer: () => mock })
return server.listen({ port: 8080 }).then(function () {
assert.deepEqual(server.addresses(), [addr])
return server.close()
})
})
})
describe('.niceAddresses()', function () {
it('returns the list of human readable addresses the server is listening at', function () {
const mock = createMock({ address: '127.0.0.1', port: 8080 })
const server = HttpServerPlus.create({ createServer: () => mock })
return server.listen({ hostname: '127.0.0.1', port: 8080 }).then(function (address) {
assert.deepEqual(server.niceAddresses(), [address])
return server.close()
})
})
})
describe('secureConnection event forwarding', function () {
function createMockHttpsServer() {
const server = new EventEmitter()
server.listen = function () {
setImmediate(() => this.emit('listening'))
}
server.address = () => ({ address: '127.0.0.1', port: 8443 })
server.close = function () {
setImmediate(() => this.emit('close'))
}
return server
}
function createServerWithMock() {
let mockServer
const server = HttpServerPlus.create({
createSecureServer: () => {
mockServer = createMockHttpsServer()
return mockServer
},
})
return {
server,
getMock: () => mockServer,
}
}
it('forwards secureConnection to listeners registered before listen()', function () {
const { server, getMock } = createServerWithMock()
const received = []
server.on('secureConnection', socket => received.push(socket))
const fakeSocket = {}
return server.listen({ cert: 'fake-cert', key: 'fake-key' }).then(function () {
getMock().emit('secureConnection', fakeSocket)
assert.deepEqual(received, [fakeSocket])
return server.close()
})
})
it('forwards secureConnection to listeners registered after listen()', function () {
const { server, getMock } = createServerWithMock()
return server.listen({ cert: 'fake-cert', key: 'fake-key' }).then(function () {
const received = []
server.on('secureConnection', socket => received.push(socket))
const fakeSocket = {}
getMock().emit('secureConnection', fakeSocket)
assert.deepEqual(received, [fakeSocket])
return server.close()
})
})
it('stops forwarding secureConnection after listener is removed', function () {
const { server, getMock } = createServerWithMock()
const received = []
const listener = socket => received.push(socket)
server.on('secureConnection', listener)
return server.listen({ cert: 'fake-cert', key: 'fake-key' }).then(function () {
server.off('secureConnection', listener)
getMock().emit('secureConnection', {})
assert.deepEqual(received, [])
return server.close()
})
})
})
describe('.listen()', function () {
it('can use a host:port', function () {
let listenArgs
const mock = createMock({ address: '127.0.0.1', port: 8080 })
mock.listen = function (...args) {
listenArgs = args
setImmediate(() => this.emit('listening'))
}
const server = HttpServerPlus.create({ createServer: () => mock })
return server.listen({ hostname: '127.0.0.1', port: 8080 }).then(function () {
assert.deepEqual(listenArgs, [8080, '127.0.0.1'])
return server.close()
})
})
it('can use a systemd socket', function () {
const SD_LISTEN_FDS_START = 3
const SOCKET_INDEX = 0
let listenArgs
const mock = createMock('http://<fd:3>')
mock.listen = function (...args) {
listenArgs = args
setImmediate(() => this.emit('listening'))
}
const server = HttpServerPlus.create({ createServer: () => mock })
const env = process.env
env.LISTEN_FDS = String(SOCKET_INDEX + 1)
env.LISTEN_PID = String(process.pid)
return server.listen({ systemdSocket: SOCKET_INDEX }).then(function () {
delete env.LISTEN_FDS
delete env.LISTEN_PID
assert.deepEqual(listenArgs, [{ fd: SOCKET_INDEX + SD_LISTEN_FDS_START }])
return server.close()
})
})
it('can use a socket', function () {
const socketPath = '/tmp/http-server-plus-test.sock'
let listenArgs
const mock = createMock(socketPath)
mock.listen = function (...args) {
listenArgs = args
setImmediate(() => this.emit('listening'))
}
const server = HttpServerPlus.create({ createServer: () => mock })
return server.listen({ socket: socketPath }).then(function () {
assert.deepEqual(listenArgs, [resolvePath(socketPath)])
return server.close()
})
})
it('can use a HTTPS certificate', function () {
let capturedOpts
const mock = createMock({ address: '127.0.0.1', port: 8443 })
const server = HttpServerPlus.create({
createSecureServer: opts => {
capturedOpts = opts
return mock
},
})
return server.listen({ cert: 'my-cert', key: 'my-key', port: 8443 }).then(function (address) {
assert.equal(capturedOpts.cert, 'my-cert')
assert.equal(capturedOpts.key, 'my-key')
assert.ok(address.startsWith('https://'), `expected https URL, got: ${address}`)
return server.close()
})
})
it('returns a promise which will resolve once listening', function () {
const mock = createMock({ address: '127.0.0.1', port: 8080 })
const server = HttpServerPlus.create({ createServer: () => mock })
return server.listen({ port: 8080 }).then(function (address) {
assert.equal(typeof address, 'string')
assert.ok(address.startsWith('http://'), `expected http URL, got: ${address}`)
return server.close()
})
})
it('returns a promise which will reject if failure', function () {
const listenError = new Error('EADDRINUSE')
const mock = createMock(null)
mock.listen = function () {
setImmediate(() => this.emit('error', listenError))
}
const server = HttpServerPlus.create({ createServer: () => mock })
return server.listen({ port: 8080 }).then(
function () {
assert.fail('should have rejected')
},
function (err) {
assert.equal(err, listenError)
assert.equal(typeof err.niceAddress, 'string')
}
)
})
})
describe('.close()', function () {
it('closes all servers', function () {
const closed = []
let callCount = 0
const server = HttpServerPlus.create({
createServer: () => {
const id = callCount++
const mock = createMock({ address: '127.0.0.1', port: id })
mock.close = function () {
closed.push(id)
setImmediate(() => this.emit('close'))
}
return mock
},
})
return Promise.all([server.listen({ port: 0 }), server.listen({ port: 0 })])
.then(function () {
return server.close()
})
.then(function () {
assert.equal(closed.length, 2)
})
})
it('emit the `close` event when all servers are closed', function () {
const mock = createMock({ address: '127.0.0.1', port: 8080 })
const server = HttpServerPlus.create({ createServer: () => mock })
let closeEmitted = false
server.on('close', () => {
closeEmitted = true
})
return server
.listen({ port: 8080 })
.then(function () {
return server.close()
})
.then(function () {
assert.ok(closeEmitted)
})
})
it('emit the `close` event even if there are no servers', function () {
const server = HttpServerPlus.create()
// close() with no servers returns Promise.resolve() immediately (before emitting
// 'close' via setImmediate), so we must wait for the event itself, not the promise.
return new Promise(function (resolve) {
server.close(resolve)
})
})
})
})

View File

@@ -0,0 +1,38 @@
{
"name": "@vates/http-server-plus",
"version": "2.0.2",
"license": "ISC",
"description": "Augmented `http.Server`, HTTP/HTTPS/HTTP2 and multiple ports on the same instance",
"keywords": [
"http",
"https",
"server",
"ports"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/http-server-plus",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@vates/http-server-plus",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"engines": {
"node": ">=20"
},
"dependencies": {
"promise-toolbox": "^0.21.0"
},
"devDependencies": {
"eslint-plugin-n": "^15.1.0"
},
"scripts": {
"pretest": "eslint .",
"test": "node --test index.spec.js",
"postversion": "npm publish --access public"
},
"private": false,
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
}
}

View File

@@ -0,0 +1,217 @@
import { RandomAccessDisk } from '@xen-orchestra/disk-transform'
import NbdClient from '@vates/nbd-client'
/**
* @typedef {{ offset: number, length: number }} DataRange
*/
/**
* @extends {RandomAccessDisk}
*/
export class NbdDisk extends RandomAccessDisk {
#nbdInfos
/** @type {NbdClient|undefined} */
#nbdClient
/** @type {Array<DataRange> | undefined} */
#dataMap
/** @type {number} */
#blockSize
/** @type {number | undefined} */
#hasBlockCursor
/** @type {number | undefined} */
#hasBlockPreviousIndex
constructor(nbdInfos, blockSize, { dataMap } = {}) {
super()
this.#blockSize = blockSize
this.#nbdInfos = nbdInfos
this.#dataMap = dataMap && this.#processDatamap(dataMap)
}
#processDatamap(rawDataMap) {
const ranges = rawDataMap
.filter(({ type, length }) => type === 0 && length > 0)
.map(({ offset, length }) => ({ offset, length }))
.sort(({ offset: offset1 }, { offset: offset2 }) => offset1 - offset2)
// hasBlock()'s forward-only cursor is only correct if the extents are
// sorted AND disjoint: an earlier extent must never reach into a block
// after a later one, otherwise the cursor could skip it and wrongly report
// a block as empty, dropping data and corrupting the output.
// Touching extents (cur.offset === prevEnd) are merged
const merged = []
for (const range of ranges) {
const last = merged[merged.length - 1]
if (last !== undefined) {
const lastEnd = last.offset + last.length
if (range.offset < lastEnd) {
throw new Error(
`overlapping ranges in data map: [${last.offset}, ${lastEnd}) and [${range.offset}, ${range.offset + range.length})`
)
}
if (range.offset === lastEnd) {
// touching: extend the previous extent
last.length += range.length
continue
}
}
// gap (or first range): new extent
merged.push(range)
}
return merged
}
/**
* @param {number} index
* @returns {Promise<DiskBlock>}
*/
async readBlock(index) {
if (!this.#nbdClient) {
throw new Error("can't readBlock before init")
}
let data = await this.#nbdClient.readBlock(index, this.getBlockSize())
if (data.length !== this.getBlockSize()) {
// this condition only work on non aligned disks
// we won't accept truncated block from unaligned disks
if (index === Math.ceil(this.getVirtualSize() / this.getBlockSize()) - 1) {
// last block add zeros at the end
data = Buffer.concat([data], this.getBlockSize())
} else {
throw new Error(`Block ${index} is not at the right size, expecting ${this.getBlockSize()}, got ${data.length}`)
}
}
return {
index,
data,
}
}
/**
* @returns {number}
*/
getVirtualSize() {
if (!this.#nbdClient) {
throw new Error("can't get size before init")
}
return Number(this.#nbdClient.exportSize)
}
/**
* @returns {number}
*/
getBlockSize() {
return this.#blockSize
}
/**
* @returns {Promise<void>}
*/
async init() {
this.#nbdClient = new NbdClient(this.#nbdInfos)
await this.#nbdClient.connect()
if (this.#dataMap === undefined) {
this.#dataMap = this.#processDatamap(await this.#nbdClient.getMap())
}
}
/**
* @returns {Promise<void>}
*/
async close() {
await this.#nbdClient?.disconnect()
}
/**
* @returns {boolean}
*/
isDifferencing() {
return false
}
/**
* @returns {Array<number>}
*/
getBlockIndexes() {
const indexes = new Set()
this.#dataMap?.forEach(({ offset, length }) => {
if (length === 0) return
const firstBlockIndex = Math.floor(offset / this.getBlockSize())
const lastBlockIndex = Math.floor((offset + length - 1) / this.getBlockSize())
for (let blockIndex = firstBlockIndex; blockIndex <= lastBlockIndex; blockIndex++) {
indexes.add(blockIndex)
}
})
return [...indexes].sort((a, b) => a - b)
}
/**
* Counts the allocated blocks without materializing the index list.
*
* @returns {number}
*/
getBlockIndexesCount() {
if (!this.#dataMap) {
throw new Error("can't getBlockIndexesCount before init")
}
const blockSize = this.getBlockSize()
let count = 0
let lastCountedBlock = -1
for (const { offset, length } of this.#dataMap) {
const firstBlockIndex = Math.floor(offset / blockSize)
const lastBlockIndex = Math.floor((offset + length - 1) / blockSize)
const from = Math.max(firstBlockIndex, lastCountedBlock + 1)
if (lastBlockIndex >= from) {
count += lastBlockIndex - from + 1
lastCountedBlock = lastBlockIndex
}
}
return count
}
/**
* @param {number} index
* @returns {boolean}
*/
hasBlock(index) {
if (!this.#dataMap) {
throw new Error("can't hasBlock before init")
}
const blockSize = this.getBlockSize()
const blockStart = index * blockSize
const blockEnd = blockStart + blockSize
let startExtentIndex = 0
if (this.#hasBlockCursor !== undefined && index >= this.#hasBlockPreviousIndex) {
startExtentIndex = this.#hasBlockCursor
} else {
this.#hasBlockCursor = undefined
}
this.#hasBlockPreviousIndex = index
const dataMap = this.#dataMap
const l = dataMap.length
for (let i = startExtentIndex; i < l; i++) {
const { offset, length } = dataMap[i]
if (offset >= blockEnd) {
// extents are sorted: nothing from here on can overlap this block
break
}
if (offset + length > blockStart) {
this.#hasBlockCursor = i
return true
}
}
return false
}
}

View File

@@ -0,0 +1,127 @@
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { NbdDisk } from './NbdDisk.mjs'
const KiB = 1024
const MiB = 1024 * KiB
const TiB = 1024 * 1024 * MiB
const SOURCE_BLOCK_SIZE = 2 * MiB // VHD_BLOCK_SIZE, the block size used when importing from VMware
const CLUSTER_SIZE = 64 * KiB // qcow2 cluster size
// hasBlock() only needs the dataMap (passed through the constructor) and the
// block size, so we can exercise it without a live NBD connection.
describe('NbdDisk.hasBlock', () => {
it('reports allocated and unallocated blocks correctly', () => {
const dataMap = [
{ type: 0, offset: 0, length: SOURCE_BLOCK_SIZE }, // block 0
{ type: 0, offset: 5 * SOURCE_BLOCK_SIZE, length: SOURCE_BLOCK_SIZE }, // block 5
]
const disk = new NbdDisk({}, SOURCE_BLOCK_SIZE, { dataMap })
assert.strictEqual(disk.hasBlock(0), true)
assert.strictEqual(disk.hasBlock(1), false)
assert.strictEqual(disk.hasBlock(4), false)
assert.strictEqual(disk.hasBlock(5), true)
assert.strictEqual(disk.hasBlock(6), false)
})
it('handles repeated and out-of-order queries with the resume cursor', () => {
const dataMap = [
{ type: 0, offset: 0, length: SOURCE_BLOCK_SIZE }, // block 0
{ type: 0, offset: 5 * SOURCE_BLOCK_SIZE, length: SOURCE_BLOCK_SIZE }, // block 5
]
const disk = new NbdDisk({}, SOURCE_BLOCK_SIZE, { dataMap })
// repeated indexes (the qcow2 path queries each 2MB block ~32 times as it
// walks 64KB clusters) must return a stable answer
assert.strictEqual(disk.hasBlock(5), true)
assert.strictEqual(disk.hasBlock(5), true)
assert.strictEqual(disk.hasBlock(6), false)
assert.strictEqual(disk.hasBlock(6), false)
// querying backwards must still be correct (falls back to a full scan)
assert.strictEqual(disk.hasBlock(0), true)
assert.strictEqual(disk.hasBlock(5), true)
})
it('invalidates the cursor on a backward miss (no stale skip)', () => {
const dataMap = [
{ type: 0, offset: 3 * SOURCE_BLOCK_SIZE, length: SOURCE_BLOCK_SIZE }, // block 3
{ type: 0, offset: 10 * SOURCE_BLOCK_SIZE, length: SOURCE_BLOCK_SIZE }, // block 10
]
const disk = new NbdDisk({}, SOURCE_BLOCK_SIZE, { dataMap })
assert.strictEqual(disk.hasBlock(10), true) // forward hit: cursor parks on the high extent
assert.strictEqual(disk.hasBlock(1), false) // backward miss: must invalidate the cursor
assert.strictEqual(disk.hasBlock(3), true) // lower extent must not be skipped
})
it('throws on overlapping ranges (would break the resume cursor)', () => {
const dataMap = [
{ type: 0, offset: 0, length: SOURCE_BLOCK_SIZE },
{ type: 0, offset: SOURCE_BLOCK_SIZE / 2, length: SOURCE_BLOCK_SIZE }, // overlaps the previous range
]
assert.throws(() => new NbdDisk({}, SOURCE_BLOCK_SIZE, { dataMap }), /overlapping ranges/)
})
it('merges touching ranges and drops zero-length / non type-0 entries', () => {
const dataMap = [
{ type: 0, offset: 2 * SOURCE_BLOCK_SIZE, length: SOURCE_BLOCK_SIZE }, // [2, 3)
{ type: 0, offset: 0, length: SOURCE_BLOCK_SIZE / 2 }, // [0, .5)
{ type: 0, offset: SOURCE_BLOCK_SIZE, length: SOURCE_BLOCK_SIZE }, // [1, 2)
{ type: 0, offset: SOURCE_BLOCK_SIZE / 2, length: SOURCE_BLOCK_SIZE / 2 }, // [.5, 1), touches [0, .5)
{ type: 1, offset: 10 * SOURCE_BLOCK_SIZE, length: SOURCE_BLOCK_SIZE }, // wrong type: ignored
{ type: 0, offset: 7 * SOURCE_BLOCK_SIZE, length: 0 }, // zero length: ignored
]
const disk = new NbdDisk({}, SOURCE_BLOCK_SIZE, { dataMap })
assert.strictEqual(disk.hasBlock(0), true)
assert.strictEqual(disk.hasBlock(1), true)
assert.strictEqual(disk.hasBlock(2), true)
assert.strictEqual(disk.hasBlock(3), false)
assert.strictEqual(disk.hasBlock(7), false) // zero-length range dropped
assert.strictEqual(disk.hasBlock(10), false) // non type-0 range dropped
})
})
describe('NbdDisk.getBlockIndexesCount', () => {
const cases = {
'aligned, disjoint blocks': [
{ type: 0, offset: 0, length: SOURCE_BLOCK_SIZE }, // block 0
{ type: 0, offset: 5 * SOURCE_BLOCK_SIZE, length: SOURCE_BLOCK_SIZE }, // block 5
],
'a range spanning several blocks': [
{ type: 0, offset: SOURCE_BLOCK_SIZE / 2, length: 3 * SOURCE_BLOCK_SIZE }, // blocks 0,1,2,3
],
'two disjoint extents sharing block 0 (needs dedup)': [
{ type: 0, offset: 0, length: SOURCE_BLOCK_SIZE / 2 }, // [0, .5) => block 0
{ type: 0, offset: 3 * (SOURCE_BLOCK_SIZE / 4), length: SOURCE_BLOCK_SIZE }, // [.75, 1.75) => blocks 0,1
],
'touching sub-block fragments': [
{ type: 0, offset: 0, length: SOURCE_BLOCK_SIZE / 2 },
{ type: 0, offset: SOURCE_BLOCK_SIZE / 2, length: SOURCE_BLOCK_SIZE / 2 },
{ type: 0, offset: SOURCE_BLOCK_SIZE, length: SOURCE_BLOCK_SIZE },
],
empty: [],
}
for (const [name, dataMap] of Object.entries(cases)) {
it(`matches getBlockIndexes().length: ${name}`, () => {
const disk = new NbdDisk({}, SOURCE_BLOCK_SIZE, { dataMap })
assert.strictEqual(disk.getBlockIndexesCount(), disk.getBlockIndexes().length)
})
}
it('matches getBlockIndexes().length on the large sparse map', () => {
const virtualSize = Math.floor(3.5 * TiB)
const NB_RANGES = 113528
const dataMap = []
for (let i = 0; i < NB_RANGES; i++) {
dataMap.push({ type: 0, offset: Math.floor((i / NB_RANGES) * virtualSize), length: CLUSTER_SIZE })
}
const disk = new NbdDisk({}, SOURCE_BLOCK_SIZE, { dataMap })
assert.strictEqual(disk.getBlockIndexesCount(), disk.getBlockIndexes().length)
})
})

View File

@@ -20,6 +20,7 @@ import {
OPTS_MAGIC,
NBD_CMD_DISC,
} from './constants.mjs'
import { spawn } from 'node:child_process'
const { warn } = createLogger('vates:nbd-client')
@@ -352,10 +353,56 @@ export default class NbdClient {
async readBlock(index, size = NBD_DEFAULT_BLOCK_SIZE) {
return pRetry(() => this.#readBlock(index, size), {
tries: this.#readBlockRetries,
when: error => error.code !== 'ERR_ABORTED',
onRetry: async err => {
warn('will retry reading block ', index, err)
await this.reconnect()
},
})
}
/**
* returns the map of the file with holes, zeros and data, useful to handle efficiently sparse source
* to implement this internally: use structure response if the server supports it, and then ask for BLOCK_STATUS *
*
* @returns {Promise<{ offset: number, length: number, type: number }[]>}
* A promise that resolves to an array where each object represents a segment:
* - `offset` — The byte offset from the start.
* - `length` — The size of the segment in bytes.
* - `type` — A numeric code indicating the segment type ( 0 means data).
*/
/* async */ getMap(signal) {
return new Promise((resolve, reject) => {
const process = spawn('nbdinfo', [
'--json',
'--map',
`nbd://${this.#serverAddress}:${this.#serverPort}/${encodeURIComponent(this.#exportName)}`,
])
let text = ''
let errText = ''
process.stdout.on('data', data => (text += data))
process.stderr.on('data', data => (errText += data))
const onAbort = () => {
process.kill()
reject(signal.reason)
}
signal?.addEventListener('abort', onAbort, { once: true })
process.on('error', err => {
signal?.removeEventListener('abort', onAbort)
reject(err)
})
process.on('close', code => {
signal?.removeEventListener('abort', onAbort)
if (code !== 0) {
return reject(new Error(`Error during getMap (code: ${code}): ${errText}`))
}
try {
const json = JSON.parse(text)
resolve(json)
} catch (error) {
reject(new Error(`${error} ${text}`))
}
})
})
}
}

View File

@@ -129,4 +129,17 @@ export default class MultiNbdClient {
yield readAhead.shift()
}
}
/**
* returns the map of the file with holes, zeros and data, useful to handle efficiently sparse source *
*
* @returns {Promise<{ offset: number, length: number, type: number }[]>}
* A promise that resolves to an array where each object represents a segment:
* - `offset` — The byte offset from the start.
* - `length` — The size of the segment in bytes.
* - `type` — A numeric code indicating the segment type (0 means no data).
*/
async getMap(signal) {
// ask the map from one of the connected client
return this.#clients[Math.floor(this.#clients.length * Math.random())].getMap(signal)
}
}

View File

@@ -13,18 +13,19 @@
"url": "https://vates.fr"
},
"license": "ISC",
"version": "3.1.4",
"version": "3.5.0",
"engines": {
"node": ">=14.0"
},
"main": "./index.mjs",
"dependencies": {
"@vates/async-each": "^1.0.0",
"@vates/read-chunk": "^1.2.0",
"@xen-orchestra/async-map": "^0.1.2",
"@xen-orchestra/log": "^0.7.1",
"@vates/async-each": "^1.0.3",
"@vates/read-chunk": "^1.2.1",
"@xen-orchestra/async-map": "^0.1.3",
"@xen-orchestra/disk-transform": "^1.3.3",
"@xen-orchestra/log": "^0.7.2",
"promise-toolbox": "^0.21.0",
"xen-api": "^4.7.3"
"xen-api": "^4.8.0"
},
"devDependencies": {
"tap": "^18.7.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@vates/node-vsphere-soap",
"version": "2.1.2",
"version": "2.1.3",
"description": "interface to vSphere SOAP/WSDL from node for interfacing with vCenter or ESXi, forked from node-vsphere-soap",
"main": "lib/client.mjs",
"author": "reedog117",
@@ -11,8 +11,8 @@
},
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"dependencies": {
"@xen-orchestra/log": "^0.7.1",
"axios": "^1.8.0",
"@xen-orchestra/log": "^0.7.2",
"axios": "^1.15.0",
"soap": "^1.0.0",
"soap-cookie": "^0.10.1"
},

View File

@@ -71,13 +71,17 @@ exports.readChunk = readChunk
* If `size` bytes are not available to be read, the returned promise is rejected.
*
* @param {Readable} stream - A readable stream to read from.
* @param {number} [size] - The number of bytes to read for binary streams (ignored for object streams).
* @param {number?} [size] - The number of bytes to read for binary streams (ignored for object streams).
* @returns {Promise<Buffer|string|unknown>} - A Promise that resolves to the read chunk.
*/
exports.readChunkStrict = async function readChunkStrict(stream, size) {
const chunk = await readChunk(stream, size)
if (chunk === null) {
throw new Error('stream has ended without data')
if (size === undefined) {
throw new Error(`stream has ended without data`)
} else {
throw new Error(`stream has ended without data, was looking for ${size} bytes`)
}
}
if (size !== undefined && chunk.length !== size) {

View File

@@ -19,7 +19,7 @@
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"version": "1.2.0",
"version": "1.2.1",
"engines": {
"node": ">=8.10"
},

View File

@@ -23,7 +23,7 @@ exports.makeOnProgress = function ({ onRootTaskEnd = noop, onRootTaskStart = noo
Object.defineProperty(taskLog, '$root', { value: taskLog })
// start of a root task
onRootTaskStart(taskLog)
onRootTaskStart(taskLog, event)
} else {
// start of a subtask
const parent = taskLogs.get(parentId)
@@ -54,10 +54,10 @@ exports.makeOnProgress = function ({ onRootTaskEnd = noop, onRootTaskStart = noo
}
if (type === 'end' && taskLog.$root === taskLog) {
onRootTaskEnd(taskLog)
onRootTaskEnd(taskLog, event)
}
}
onTaskUpdate(taskLog)
onTaskUpdate(taskLog, event)
}
}

View File

@@ -7,6 +7,27 @@ function alreadyEnded() {
throw new Error('task has already ended')
}
const serializeErrors = errors => (Array.isArray(errors) ? errors.map(serializeError) : errors)
// Create a serializable object from an error.
//
// Otherwise some fields might be non-enumerable and missing from logs.
const serializeError = error => {
return error instanceof Error
? {
...error, // Copy enumerable properties.
cause: error.cause,
code: error.code,
errors: serializeErrors(error.errors), // supports AggregateError
message: error.message,
name: error.name,
stack: error.stack,
}
: error
}
exports.serializeError = serializeError
// define a read-only, non-enumerable, non-configurable property
function define(object, property, value) {
Object.defineProperty(object, property, { value })
@@ -72,7 +93,11 @@ class Task {
_onProgress
get id() {
return (this.id = Math.random().toString(36).slice(2))
// Use a compact, sortable, string representation of the creation date,
// and add a random part to differentiate tasks created at the same time
//
// Due to the padding, dates are sortable up to 5188-04-22T11:04:28.415Z
return (this.id = Date.now().toString(36).padStart(9, '0') + '-' + Math.random().toString(36).slice(2))
}
set id(value) {
define(this, 'id', value)
@@ -136,6 +161,10 @@ class Task {
assert.equal(this.#status, PENDING)
assert.equal(this.#running, false)
if (result instanceof Error && result.toJSON === undefined) {
result = serializeError(result)
}
this.#emit('end', { status, result })
this._onProgress = alreadyEnded
this.#status = status

View File

@@ -4,7 +4,7 @@ const assert = require('node:assert').strict
const { describe, it } = require('node:test')
const { makeOnProgress } = require('./combineEvents.js')
const { Task } = require('./index.js')
const { Task, serializeError } = require('./index.js')
const noop = Function.prototype
@@ -28,6 +28,36 @@ function createTask(opts) {
return task
}
describe('serializeError', function () {
it('serializes simple error', function () {
const err = new Error('test Error')
err.code = 404
const serialized = serializeError(err)
assert.equal(serialized.message, 'test Error')
assert.equal(serialized.name, 'Error')
assert.equal(serialized.code, 404)
assert.notEqual(serialized.stack, undefined)
})
it('serializes aggregate error', function () {
const err = new AggregateError([new Error('test error 1'), new Error('test error 2')], 'Error message', {
cause: 'broken',
})
const serialized = serializeError(err)
assert.equal(serialized.message, 'Error message')
assert.equal(serialized.name, 'AggregateError')
assert.equal(serialized.cause, 'broken')
assert.notEqual(serialized.stack, undefined)
assert.equal(serialized.errors.length, 2)
assert.equal(serialized.errors[0].name, 'Error')
assert.equal(serialized.errors[1].name, 'Error')
assert.equal(serialized.errors[0].message, 'test error 1')
assert.equal(serialized.errors[1].message, 'test error 2')
assert.notEqual(serialized.errors[0].stack, undefined)
assert.notEqual(serialized.errors[1].stack, undefined)
})
})
describe('Task', function () {
describe('constructor', function () {
it('data properties are passed to the start event', async function () {
@@ -106,7 +136,7 @@ describe('Task', function () {
assert.equal(task.$events.length, 3)
assertEvent(task, { type: 'start' }, 0)
assertEvent(task, { type: 'abortionRequested', reason }, 1)
assertEvent(task, { type: 'end', status: 'failure', result }, 2)
assertEvent(task, { type: 'end', status: 'failure', result: serializeError(result) }, 2)
})
it('does not abort if the task succeed', async function () {
@@ -176,7 +206,7 @@ describe('Task', function () {
assertEvent(task, {
status: 'failure',
result: error,
result: serializeError(error),
type: 'end',
})
})
@@ -475,7 +505,7 @@ describe('Task', function () {
assert.equal(task.status, 'failure')
assertEvent(task, {
status: 'failure',
result: e,
result: serializeError(e),
type: 'end',
})
})
@@ -511,7 +541,7 @@ describe('Task', function () {
assert.equal(task.status, 'failure')
assertEvent(task, {
status: 'failure',
result: e,
result: serializeError(e),
type: 'end',
})
})

View File

@@ -13,7 +13,7 @@
"url": "https://vates.fr"
},
"license": "ISC",
"version": "0.6.2",
"version": "0.7.0",
"engines": {
"node": ">=14"
},

View File

@@ -2,8 +2,17 @@
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export default tseslint.config(eslint.configs.recommended, tseslint.configs.recommended, {
languageOptions: {
parserOptions: {
tsconfigRootDir: __dirname,
},
},
ignores: ['dist'],
rules: {
// Allow empty interface for recursive generic typing (E.g. interface XenApiNetworkWrapped)

View File

@@ -1,7 +1,7 @@
{
"name": "@vates/types",
"private": false,
"version": "1.10.0",
"version": "1.30.1",
"main": "./dist/index.mjs",
"exports": {
".": {
@@ -9,6 +9,7 @@
"types": "./dist/index.d.mts"
},
"./xo": "./dist/xo.d.mts",
"./xo-app": "./dist/xo-app.d.mts",
"./xen-api": "./dist/xen-api.d.mts",
"./common": {
"default": "./dist/common.mjs",
@@ -21,13 +22,18 @@
"./lib/xen-orchestra/*": {
"default": "./dist/lib/xen-orchestra-*.mjs",
"type": "./dist/lib/xen-orchestra-*.d.mts"
},
"./lib/complex-matcher": {
"default": "./dist/lib/complex-matcher.mjs",
"type": "./dist/lib/complex-matcher.d.mts"
}
},
"type": "module",
"devDependencies": {
"@eslint/js": "^9.19.0",
"typescript": "~5.6.3",
"typescript-eslint": "^8.23.0"
"typescript-eslint": "^8.61.0",
"@types/express": "^5.0.0"
},
"scripts": {
"build": "tsc",

View File

@@ -1,4 +1,4 @@
import type { XoHost } from './xo.mjs'
import type { XoHost, XoNetwork, XoVif } from './xo.mjs'
declare const __brand: unique symbol
@@ -655,6 +655,14 @@ export const BACKUP_TYPE = { backup: 'backup', metadata: 'metadataBackup', mirro
export type BACKUP_TYPE = (typeof BACKUP_TYPE)[keyof typeof BACKUP_TYPE]
export const SUPPORTED_VDI_FORMAT = {
raw: 'raw',
vhd: 'vhd',
qcow2: 'qcow2',
} as const
export type SUPPORTED_VDI_FORMAT = (typeof SUPPORTED_VDI_FORMAT)[keyof typeof SUPPORTED_VDI_FORMAT]
// ----- XAPI Stats
type XapiStatsResponse<T> = {
@@ -732,3 +740,32 @@ export type XapiVmStatsRaw = {
export type XapiVmStats = XapiStatsResponse<XapiVmStatsRaw>
export type XapiPoolStats = Record<XoHost['id'], XapiHostStats | { error: Record<string, unknown> }>
export type TrafficRuleProtocol = (typeof TRAFFIC_RULE_PROTOCOLS)[number]
export const SDN_CONTROLLER_OF_RULES_KEY = 'xo:sdn-controller:of-rules'
export const SDN_CONTROLLER_OF_METHOD_KEY = 'xo:sdn-controller:of-method'
export const SDN_CONTROLLER_OF_FORMAT_KEY = 'xo:sdn-controller:of-format'
export const TRAFFIC_RULE_PROTOCOLS = ['ARP', 'ICMP', 'IP', 'TCP', 'UDP'] as const
export const TRAFFIC_RULE_PROTOCOLS_WITH_PORT: readonly TrafficRuleProtocol[] = ['TCP', 'UDP']
export type TrafficRuleTargetType = XoNetwork['type'] | XoVif['type']
export type TrafficRuleDirection = 'from' | 'to' | 'from/to'
export type RawTrafficRule = {
allow: boolean
protocol: string
ipRange: string
direction: string
port?: string
}
type BaseTrafficRule = RawTrafficRule & {
id: string
networkId: XoNetwork['id']
}
export type TrafficRule =
| (BaseTrafficRule & { type: XoVif['type']; sourceId: XoVif['id'] })
| (BaseTrafficRule & { type: XoNetwork['type']; sourceId: XoNetwork['id'] })

View File

@@ -1,4 +1,8 @@
export * from './common.mjs'
export * from './xen-api.mjs'
export * from './xo.mjs'
export * from './xo-app.mjs'
export * from './lib/xen-orchestra-xapi.mjs'
export * from './lib/xen-orchestra-acl.mjs'
export * from './lib/complex-matcher.mjs'
export * from './lib/rest-api.mjs'

View File

@@ -0,0 +1,5 @@
export type Or<T> = Id<{ __or: T[] }>
export type Id<T> = { id: T }
export type IdOr<T> = Id<T> | Or<T>

View File

@@ -0,0 +1,102 @@
import type { NextFunction, Request, Response } from 'express'
import type { VatesTask } from './vates-task.mjs'
import type { XoApp } from '../xo-app.mjs'
export type SecurityName = '*' | 'token' | 'basic' | 'none'
export type FieldDefinition =
| {
type: 'string'
example?: string
optional?: boolean
}
| {
type: 'boolean'
example?: boolean
optional?: boolean
}
| {
type: 'number'
example?: number
optional?: boolean
}
| {
type: 'enum'
enum: string[]
example?: string
optional?: boolean
}
| {
type: 'object'
fields: Record<string, FieldDefinition>
optional?: boolean
}
| {
type: 'array'
items: FieldDefinition
example?: unknown[]
optional?: boolean
}
export type ParamFieldDefinition = Exclude<
FieldDefinition,
{ type: 'boolean' } | { type: 'object' } | { type: 'array' }
>
export type QueryFieldDefinition = Exclude<FieldDefinition, { type: 'object' } | { type: 'array' }>
type RestApi = object & { xoApp: XoApp }
export type CreateAction = <Result>(
callback: (task: VatesTask) => MaybePromise<Result>,
opts: {
sync?: boolean
statusCode?: number
taskProperties: { name: string; [key: string]: unknown }
}
) => Promise<Result | undefined>
export interface BaseRouteDefinition<Middleware> {
method: 'get' | 'post' | 'put' | 'delete' | 'patch'
endpoint: string
description?: string
tags?: string[]
params?: Record<string, ParamFieldDefinition>
query?: Record<string, QueryFieldDefinition>
body?: Record<string, FieldDefinition>
responses?: Array<{
status: number
description: string
schema?: Record<string, FieldDefinition>
}>
middlewares?: Middleware[]
scope?: 'acl'
callback: (params: {
req: Request
res: Response
next: NextFunction
restApi: RestApi
createAction: CreateAction
}) => MaybePromise<unknown>
security?: SecurityName
}
export type MiddlewareDescriptor =
| { name: 'json' | 'urlencoded' | 'text' | 'raw'; options?: Record<string, unknown> }
| { name: 'acl'; acls: LooseAclEntry | LooseAclEntry[] }
type MaybePromise<T> = T | Promise<T>
/**
* Loosely-typed mirror of {@link https://github.com/vatesfr/xen-orchestra/blob/master/@xen-orchestra/rest-api/src/middlewares/acl.middleware.mts | AclEntry}
* from `@xen-orchestra/rest-api/src/middlewares/acl.middleware.mts`.
*/
export type LooseAclEntry = {
resource: string
action?: string | ((opts: { req: object; restApi: RestApi }) => string | undefined)
actions?: string[] | ((opts: { req: object; restApi: RestApi }) => string[])
objectId?: string | ((opts: { req: object; restApi: RestApi }) => string)
objectIds?: string[] | ((opts: { req: object; restApi: RestApi }) => string[])
object?: object | ((opts: { req: object; restApi: RestApi }) => MaybePromise<object> | undefined)
objects?: object[] | ((opts: { req: object; restApi: RestApi }) => MaybePromise<object[]> | undefined)
getObject?: (opts: { restApi: RestApi }) => (id: string) => MaybePromise<object>
}
export type PluginRestRouteDefinition = BaseRouteDefinition<MiddlewareDescriptor>

View File

@@ -1,7 +1,21 @@
import { Branded } from '../common.mjs'
export type Task = {
id: Branded<'task'>
export interface VatesTask {
_abortController: AbortController
_onProgress(data?: unknown): void
run: <T>(fn: () => T) => Promise<T>
id: Branded<'vates-task'>
status: 'failure' | 'pending' | 'success'
abort(reason?: unknown): void
failure(error?: unknown): void
info(message: string, data: unknown): void
run<T>(fn: () => T | (() => Promise<T>)): Promise<T>
runInside<T>(fn: () => T | (() => Promise<T>)): Promise<T>
set(name: string, value: unknown): void
start(): void
success(result?: unknown): void
warning(message: string, data: unknown): void
wrap<T>(fn: () => T | (() => Promise<T>)): Promise<T>
wrapInside<T>(fn: () => T | (() => Promise<T>)): Promise<T>
}

View File

@@ -0,0 +1,83 @@
import { Branded } from '../common.mjs'
import type { XoGroup, XoUser } from '../xo.mjs'
type XoAclBaseRole = {
id: Branded<'acl-v2-role'>
name: string
description?: string
privilegeIds: XoAclBasePrivilege['id'][]
}
type XoAclRegularRole = XoAclBaseRole & {
groupIds: XoGroup['id'][]
userIds: XoUser['id'][]
}
type XoAclTemplateRole = XoAclBaseRole & {
isTemplate: true
roleTemplateId: number
}
export type XoAclRole = XoAclRegularRole | XoAclTemplateRole
export type XoAclSupportedActionsByResource = {
[resource: string]: Record<string, unknown>
}
/**
* E.g
* vms {
* shutdown: {
* clean: true,
* hard: true,
* }
* }
*
* `shutdown | shutdown:clean | shutdown:hard`
*/
export type GetKeysRecursively<T, Prefix extends string = ''> = {
[K in keyof T]: T[K] extends object
? K extends string
? `${Prefix}${K}` | GetKeysRecursively<T[K], `${Prefix}${K}:`>
: never
: K extends string
? `${Prefix}${K}`
: never
}[keyof T]
export type XoAclSupportedResource<TActionsByResource extends XoAclSupportedActionsByResource> =
keyof TActionsByResource
export type XoAclSupportedActions<
TActionsByResource extends XoAclSupportedActionsByResource,
TResource extends XoAclSupportedResource<TActionsByResource>,
> = (GetKeysRecursively<TActionsByResource[TResource]> & string) | '*'
export type XoAclBasePrivilege = {
id: Branded<'acl-v2-privilege'>
resource: string
action: string
selector?: string
effect: 'allow' | 'deny'
roleId: XoAclRole['id']
}
export type XoAclPrivilege<
TActionsByResource extends XoAclSupportedActionsByResource,
TResource extends XoAclSupportedResource<TActionsByResource>,
> = XoAclBasePrivilege & {
resource: TResource
action: XoAclSupportedActions<TActionsByResource, TResource>
}
export type XoUserRole = {
id: string
roleId: XoAclRole['id']
userId: XoUser['id']
}
export type XoGroupRole = {
id: string
roleId: XoAclRole['id']
groupId: XoGroup['id']
}

View File

@@ -1,28 +1,104 @@
import {
WrappedXenApiRecord,
XenApiHost,
XenApiHostWrapped,
XenApiNetwork,
XenApiNetworkWrapped,
XenApiRecord,
XenApiSr,
XenApiTask,
XenApiVbd,
XenApiVdi,
XenApiVif,
XenApiVm,
XenApiVmWrapped,
XenApiVtpm,
} from '../xen-api.mjs'
import type { Readable } from 'node:stream'
import type {
BOND_MODE,
OPAQUE_REF_NULL,
SUPPORTED_VDI_FORMAT,
VBD_MODE,
VBD_TYPE,
VDI_TYPE,
VIF_LOCKING_MODE,
VM_OPERATIONS,
} from '../common.mjs'
import type { PassThrough, Readable } from 'node:stream'
import type {
XapiXoRecord,
XoGpuGroup,
XoVgpuType,
XoHost,
XoNetwork,
XoPif,
XoSr,
XoUser,
XoVdi,
XoVgpuType,
XoVm,
XoVmTemplate,
XoVif,
XoVmSnapshot,
} from '../xo.mjs'
/**
* Properties accepted by {@link Xapi.editVm}.
*
* Field names match the canonical (camelCase) properties defined by the
* underlying `_editVm` (via `makeEditObject`). `_editVm` still resolves the
* snake_case and legacy aliases, but the canonical names are used here.
*/
export interface EditVmProps {
affinityHost?: string | null
autoPoweron?: boolean
blockedOperations?: Partial<Record<VM_OPERATIONS, boolean | string | null>>
coresPerSocket?: number | string | null
cpuCap?: number | null
cpuMask?: number[]
cpuWeight?: number | null
cpus?: number
cpusStaticMax?: number | string
/**
* Update VM creation metadata stored under `other_config.xo:*`. The object is
* merged with the existing data.
*/
creation?: { user?: string }
expNestedHvm?: boolean
hasVendorDevice?: boolean
highAvailability?: 'best-effort' | 'restart' | ''
hvmBootFirmware?: string | null
memory?: number | string
memoryMax?: number | string
memoryMin?: number | string
memoryStaticMax?: number | string
nameDescription?: string
nameLabel?: string
nestedVirt?: boolean
nicType?: string | null
notes?: string | null
PV_args?: string
secureBoot?: boolean
startDelay?: number
suspendSr?: string | null
tags?: string[]
uefiMode?: 'setup' | 'user'
vga?: 'std' | 'cirrus'
videoram?: 1 | 2 | 4 | 8 | 16
viridian?: boolean
virtualizationMode?: 'pv' | 'hvm'
xenStoreData?: Record<string, string | null>
}
/** Properties accepted by {@link Xapi.editVif}. */
export interface EditVifProps {
ipv4Allowed?: string[]
ipv6Allowed?: string[]
lockingMode?: VIF_LOCKING_MODE
// in kB/s, `null` removes the limit
rateLimit?: number | null
txChecksumming?: boolean
}
export type XcpPatches = {
changelog?: {
author: string
@@ -53,14 +129,29 @@ export type XsPatches = {
}
export interface Xapi {
status: string
pool?: { uuid: string }
sessionId: string
_auth: { user: string; password: string }
_url: { protocol: string; hostname: string; hostnameRaw: string; port?: string }
call: <ReturnType>(...args: unknown[]) => Promise<ReturnType>
callAsync: <ReturnType>(...args: unknown[]) => Promise<ReturnType>
barrier<T extends XenApiRecord>(ref: T['$ref']): Promise<Extract<WrappedXenApiRecord, { $ref: T['$ref'] }>>
connectVif(vifId: XoVif['id']): Promise<void>
getField<T extends XenApiRecord, K extends keyof T>(
type: Extract<WrappedXenApiRecord, T>['$type'],
ref: T['$ref'],
field: K
): Promise<T[K]>
getObject: <
XoRecord extends XapiXoRecord,
WrappedRecord extends WrappedXenApiRecord = Extract<WrappedXenApiRecord, { $type: XoRecord['type'] }>,
>(
idOrUuidOrRef: XoRecord['id'] | WrappedRecord['$ref'] | WrappedRecord['uuid'],
defaultValue?: WrappedRecord
) => WrappedRecord
createNetwork(
params:
| {
@@ -77,11 +168,125 @@ export interface Xapi {
vlan: number
}
): Promise<XenApiNetworkWrapped>
createBondedNetwork(params: {
bondMode: BOND_MODE
pifIds: XoPif['id'][]
name: string
description?: string
mtu?: number
}): Promise<XenApiNetworkWrapped>
deleteNetwork(id: XoNetwork['id']): Promise<void>
deleteVif(vifId: XoVif['id']): Promise<void>
disconnectVif(vifId: XoVif['id']): Promise<void>
exportVmOva(vmRef: XenApiVm['$ref']): Promise<PassThrough>
emergencyShutdownHost(hostId: XoHost['id']): Promise<void>
ejectHostFromPool(hostId: XoHost['id']): Promise<void>
host_smartReboot(
hostRef: XenApiHost['$ref'],
bypassBlockedSuspend?: boolean,
bypassCurrentVmCheck?: boolean
): Promise<void>
migrateVm(
vmId: XoVm['id'],
hostXapi: Xapi,
hostId: XoHost['id'],
opts?: {
force?: boolean
mapVdisSrs?: Record<XoVdi['id'], XoSr['id']>
mapVifsNetworks?: Record<XoVif['id'], XoNetwork['id']>
migrationNetworkId?: XoNetwork['id']
sr?: XoSr['id']
bypassAssert?: boolean
}
): Promise<void>
joinPool(masterAddress: string, masterUsername: string, masterPassword: string, force?: boolean): Promise<void>
listMissingPatches(host: XoHost['id']): Promise<XcpPatches[] | XsPatches[]>
pool_emergencyShutdown(): Promise<void>
resumeVm(id: XoVm['id']): Promise<void>
revertVm(snapshotId: XoVmSnapshot['id']): Promise<void>
unpauseVm(id: XoVm['id']): Promise<void>
cloneVm(
vmId: XoVm['id'],
opts?: {
nameLabel?: string
fast?: boolean
}
): Promise<XenApiVmWrapped>
copyVm(
vmId: XoVm['id'],
opts?: {
nameLabel?: string
srOrSrId?: XoSr['id'] | XoSr
}
): Promise<XenApiVmWrapped>
remoteCopyVm(
vmId: XoVm['id'],
targetXapi: Xapi,
targetSrId: XoSr['id'],
opts?: {
compress?: 'zstd' | 'gzip' | boolean
nameLabel?: string
}
): Promise<{ vm: XenApiVmWrapped }>
forgetSr(id: XoSr['id']): Promise<void>
forgetHost(hostId: XoHost['id']): Promise<void>
powerOnHost(hostId: XoHost['id']): Promise<void>
rebootHost(hostId: XoHost['id'], force?: boolean): Promise<void>
shutdownHost(hostId: XoHost['id'], opts?: { force?: boolean; bypassEvacuate?: boolean }): Promise<void>
SR_create(params: {
content_type?: XoSr['content_type']
// index signature, not `Record<string, string>`: tsoa emits an empty schema for `Record`
// (https://github.com/lukeautry/tsoa/pull/1858). device_config keys are driver-specific (SR_type).
device_config: {
chapuser?: string
chappassword?: string
device?: string
legacy_mode?: string
location?: string
nfsversion?: string
options?: string
password?: string
SCSIid?: string
server?: string
serverpath?: string
target?: string
targetIQN?: string
username?: string
} & {
[key: string]: string
}
host: XenApiHost['$ref']
name_description?: XoSr['name_description']
name_label: XoSr['name_label']
physical_size?: number
shared: boolean
sm_config?: { [key: string]: string }
type: XoSr['SR_type']
}): Promise<XenApiSr['$ref']>
SR_importVdi(
ref: XenApiSr['$ref'],
stream: Readable,
opts?: {
format?: SUPPORTED_VDI_FORMAT
name_description?: XoVdi['name_description']
name_label?: XoVdi['name_label']
other_config?: XoVdi['other_config']
read_only?: boolean
sharable?: boolean
SR?: XenApiSr['$ref']
tags?: XoVdi['tags']
type?: XoVdi['type']
virtual_size?: XoVdi['size']
xenstore_data?: Record<string, string>
}
): Promise<XenApiVdi['$ref']>
SR_reclaimSpace(ref: XenApiSr['$ref']): Promise<void>
editVm(
id: XoVm['id'],
props: EditVmProps,
checkLimits?: (limits: Record<string, number>, vm: XenApiVmWrapped) => Promise<void>
): Promise<void>
editVif(id: XoVif['id'], props: EditVifProps): Promise<void>
startVm(
id: XoVm['id'],
opts?: {
@@ -97,6 +302,10 @@ export interface Xapi {
startOnly?: boolean
}
): Promise<void>
VM_export(
vmRef: XenApiVm['$ref'],
opts?: { cancelToken?: unknown; compress?: boolean | 'zstd' | 'gzip'; useSnapshot?: boolean }
): ReturnType<Xapi['getResource']>
VM_import(
stream: Readable,
srRef?: XenApiSr['$ref'],
@@ -107,6 +316,7 @@ export interface Xapi {
metadataVm: {
affinityHost?: XoHost['id']
autoPoweron?: boolean
cpus?: number
memory?: number
name_description?: string
name_label: string
@@ -148,12 +358,80 @@ export interface Xapi {
vgpuType?: XoVgpuType['id']
gpuGroup?: XoGpuGroup['id']
copyHostBiosStrings?: boolean
highAvailability?: XoVm['high_availability']
hvmBootFirmware?: 'uefi' | 'bios'
secureBoot?: boolean
},
checkLimits?: boolean,
creatorId?: XoUser['id'],
opts?: { destroyAllVifs: boolean }
): Promise<XenApiVmWrapped>
moveVdi(
vdiId: XenApiVdi['$ref'] | XoVdi['id'],
srId: XenApiSr['$ref'] | XoSr['id'],
opts?: { _failOnCbtError?: boolean }
): Promise<XenApiVdi>
VBD_create(params: {
bootable?: boolean
empty?: boolean
mode?: VBD_MODE
other_config?: Record<string, string>
qos_algorithm_params?: Record<string, string>
qos_algorithm_type?: string
type?: VBD_TYPE
unpluggable?: boolean
userdevice?: string
VDI: XenApiVdi['$ref'] | OPAQUE_REF_NULL
VM: XenApiVm['$ref']
}): Promise<XenApiVbd['$ref']>
VBD_destroy(vbdRef: XenApiVbd['$ref']): Promise<void>
VBD_unplug(vbdRef: XenApiVbd['$ref']): Promise<void>
VDI_create(
options: {
name_description?: XoVdi['name_description']
name_label?: XoVdi['name_label']
other_config?: XoVdi['other_config']
read_only?: boolean
sharable?: boolean
SR?: XenApiSr['$ref']
tags?: XoVdi['tags']
type?: VDI_TYPE
virtual_size: XoVdi['size']
xenstore_data?: Record<string, string>
},
extraOptions?: {
sm_config?: Record<string, string>
}
): Promise<XenApiVdi['$ref']>
VDI_destroy(vdiRef: XenApiVdi['$ref']): Promise<void>
VDI_destroyCloudInitConfig(vdiRef: XenApiVdi['$ref'], opts?: { timeLimit?: number }): Promise<void>
VDI_exportContent(
vdiRef: XenApiVdi['$ref'],
opts: { baseRef?: string; cancelToken?: unknown; format: SUPPORTED_VDI_FORMAT }
): Promise<Readable & { length?: number }>
VDI_importContent(
vdiRef: XenApiVdi['$ref'],
stream: Readable | Buffer,
opts: { cancelToken?: unknown; format: SUPPORTED_VDI_FORMAT }
): Promise<void>
VIF_create(
options: {
currently_attached?: boolean
device?: string
ipv4_allowed?: string[]
ipv6_allowed?: string[]
locking_mode?: VIF_LOCKING_MODE
MTU?: number
network: XenApiNetwork['$ref']
other_config?: Record<string, string>
qos_algorithm_params?: Record<string, string>
qos_algorithm_type?: string
VM: XenApiVm['$ref']
},
extraOptions?: {
MAC?: string
}
): Promise<XenApiVif['$ref']>
VM_createCloudInitConfig(
vmRef: XenApiVm['$ref'],
cloudConfig: string,
@@ -172,5 +450,29 @@ export interface Xapi {
pathname: string,
params?: { host?: XenApiHost; query?: Record<string, unknown>; task?: boolean | XenApiTask['$ref'] }
): Promise<{ body: Readable }>
clearHost(
host: Pick<XenApiHostWrapped, '$ref' | '$pool'>,
force?: boolean,
opts?: { transient?: boolean }
): Promise<void>
disableHost(hostId: XoHost['id'], opts?: { transient?: boolean }): Promise<void>
enableHost(hostId: XoHost['id']): Promise<void>
getRecordByUuid<
Type extends WrappedXenApiRecord['$type'],
XenApiRecord extends WrappedXenApiRecord = Extract<WrappedXenApiRecord, { $type: Type }>,
>(
type: Type,
uuid: XenApiRecord['uuid']
): Promise<XenApiRecord>
isHyperThreadingEnabled(hostId: XoHost['id']): Promise<boolean | null>
VTPM_create(params: { VM: XenApiVm['$ref']; is_unique?: boolean; contents?: string }): Promise<XenApiVtpm['$ref']>
destroySr(id: XoSr['id']): Promise<void>
xostor_delete(ref: XenApiSr['$ref']): Promise<void>
objects: {
indexes: {
type: {
[XenApiRecord in WrappedXenApiRecord as XenApiRecord['$type']]: Record<XenApiRecord['uuid'], XenApiRecord>
}
}
}
}

View File

@@ -70,6 +70,11 @@ import type { Xapi } from './lib/xen-orchestra-xapi.mjs'
// https://github.com/vatesfr/xen-orchestra/tree/wip-xapi-types-generator/%40xen-orchestra/xapi-generator
// All properties added after XenServer 7.0 (dundee) release have been marked as optional
type TagCallMethods = {
(method: 'add_tags', tag: string): Promise<void>
(method: 'remove_tags', tag: string): Promise<void>
}
/**
* Add properties injected by `xen-api`.
* $ref property is also injected by XOLite, so she is not present here.
@@ -77,6 +82,7 @@ import type { Xapi } from './lib/xen-orchestra-xapi.mjs'
type WrapperXenApi<T, Type extends string, Fn = { (): void }> = T & {
$call: Fn
$callAsync: Fn
$pool: XenApiPool
$type: Type
$snapshot(params: {
cancelToken?: unknown
@@ -162,6 +168,8 @@ export interface XenApiEvent {
timestamp?: string
}
type XenApiPoolCallMethods = TagCallMethods & {}
export interface XenApiPool {
$ref: Branded<'pool'>
allowed_operations: POOL_ALLOWED_OPERATIONS[]
@@ -184,6 +192,7 @@ export interface XenApiPool {
ha_host_failures_to_tolerate: number
ha_overcommitted: boolean
ha_plan_exists_for: number
ha_reboot_vm_on_internal_shutdown?: boolean
ha_statefiles: string[]
health_check_config: Record<string, string>
igmp_snooping_enabled?: boolean
@@ -224,7 +233,7 @@ export interface XenApiPool {
/** @deprecated */
wlb_verify_cert?: boolean
}
export type XenApiPoolWrapped = WrapperXenApi<XenApiPool, 'pool'>
export type XenApiPoolWrapped = WrapperXenApi<XenApiPool, 'pool', XenApiPoolCallMethods>
/** @deprecated */
export interface XenApiPoolPatch {
@@ -256,7 +265,7 @@ export interface XenApiPoolUpdate {
version?: string
}
type XenApiVmCallMethods = {
type XenApiVmCallMethods = TagCallMethods & {
(method: 'start', start_paused: boolean, force: boolean): Promise<void>
(method: 'clean_shutdown'): Promise<void>
(method: 'hard_shutdown'): Promise<void>
@@ -483,6 +492,9 @@ export interface XenApiDrTask {
uuid: string
}
type XenApiHostCallMethods = TagCallMethods & {
<T>(method: 'call_plugin', plugin: string, fn: string, args: Record<string, string>): Promise<T>
}
export interface XenApiHost {
$ref: Branded<'host'>
address: string
@@ -559,7 +571,9 @@ export interface XenApiHost {
uuid: string
virtual_hardware_platform_versions: number[]
}
export type XenApiHostWrapped = WrapperXenApi<XenApiHost, 'host'>
export interface XenApiHostWrapped extends WrapperXenApi<XenApiHost, 'host', XenApiHostCallMethods> {
$restartAgent(): Promise<void>
}
export interface XenApiHostCrashdump {
$ref: Branded<'host_crashdump'>
@@ -614,6 +628,10 @@ export interface XenApiHostCpu {
vendor: string
}
type XenApiNetworkCallMethods = TagCallMethods & {
(method: 'add_purpose', purpose: NETWORK_PURPOSE): Promise<void>
}
export interface XenApiNetwork {
$ref: Branded<'network'>
allowed_operations: NETWORK_OPERATIONS[]
@@ -633,8 +651,7 @@ export interface XenApiNetwork {
uuid: string
VIFs: XenApiVif['$ref'][]
}
export interface XenApiNetworkWrapped extends WrapperXenApi<XenApiNetwork, 'network'> {}
export interface XenApiNetworkWrapped extends WrapperXenApi<XenApiNetwork, 'network', XenApiNetworkCallMethods> {}
export interface XenApiVif {
$ref: Branded<'VIF'>
allowed_operations: VIF_OPERATIONS[]
@@ -782,6 +799,7 @@ export interface XenApiSm {
}
export type XenApiSmWrapped = WrapperXenApi<XenApiSm, 'SM'>
type XenApiSrCallMethods = TagCallMethods & {}
export interface XenApiSr {
$ref: Branded<'SR'>
allowed_operations: STORAGE_OPERATIONS[]
@@ -806,7 +824,7 @@ export interface XenApiSr {
VDIs: XenApiVdi['$ref'][]
virtual_allocation: number
}
export type XenApiSrWrapped = WrapperXenApi<XenApiSr, 'SR'>
export type XenApiSrWrapped = WrapperXenApi<XenApiSr, 'SR', XenApiSrCallMethods>
export interface XenApiSrStat {
$ref: Branded<'sr_stat'>
@@ -832,6 +850,8 @@ export interface XenApiLvhd {
uuid: string
}
type XenApiVdiCallMethods = TagCallMethods & {}
export interface XenApiVdi {
$ref: Branded<'VDI'>
allow_caching: boolean
@@ -868,7 +888,7 @@ export interface XenApiVdi {
virtual_size: number
xenstore_data: Record<string, string>
}
export type XenApiVdiWrapped = WrapperXenApi<XenApiVdi, 'VDI'>
export type XenApiVdiWrapped = WrapperXenApi<XenApiVdi, 'VDI', XenApiVdiCallMethods>
export interface XenApiVbd {
$ref: Branded<'VBD'>
@@ -921,6 +941,7 @@ export interface XenApiPbd {
SR: XenApiSr['$ref']
uuid: string
}
export type XenApiPbdWrapped = WrapperXenApi<XenApiPbd, 'PBD'>
/** @deprecated */
export interface XenApiCrashdump {
@@ -995,6 +1016,7 @@ export interface XenApiMessage {
timestamp: string
uuid: string
}
export type XenApiMessageWrapped = WrapperXenApi<XenApiMessage, 'message'>
export interface XenApiSecret {
$ref: Branded<'secret'>
@@ -1357,6 +1379,7 @@ export type XenApiRecord =
export type WrappedXenApiRecord =
| XenApiHostWrapped
| XenApiNetworkWrapped
| XenApiPbdWrapped
| XenApiPifWrapped
| XenApiPoolWrapped
| XenApiSrWrapped

416
@vates/types/src/xo-app.mts Normal file
View File

@@ -0,0 +1,416 @@
import { EventEmitter } from 'node:stream'
import type {
AnyXoJob,
AnyXoLog,
XapiXoRecord,
XoAuthenticationToken,
XoBackupRepository,
XoConfigBackupArchive,
XoGroup,
XoHost,
XoPool,
XoPoolBackupArchive,
XoProxy,
XoSchedule,
XoServer,
XoTask,
XoUser,
XoVif,
XoVm,
XoVmBackupArchive,
} from './xo.mjs'
import { VatesTask } from './lib/vates-task.mjs'
import type { PluginRestRouteDefinition } from './lib/rest-api.mjs'
import {
Xapi,
XapiHostStats,
XapiPoolStats,
XapiStatsGranularity,
XapiVmStats,
XenApiGpuGroupWrapped,
XenApiHostWrapped,
XenApiMessageWrapped,
XenApiNetworkWrapped,
XenApiPbdWrapped,
XenApiPciWrapped,
XenApiPgpuWrapped,
XenApiPifWrapped,
XenApiPoolWrapped,
XenApiSmWrapped,
XenApiSrWrapped,
XenApiVbdWrapped,
XenApiVdiWrapped,
XenApiVgpuTypeWrapped,
XenApiVgpuWrapped,
XenApiVifWrapped,
XenApiVmWrapped,
XenApiVtpmWrapped,
XoAclBasePrivilege,
XoAclRole,
XoGroupRole,
XoUserRole,
} from './index.mjs'
type FeatureCode =
| 'BACKUP.DELTA'
| 'BACKUP.DELTA_REPLICATION'
| 'BACKUP.FULL'
| 'BACKUP.HEALTHCHECK'
| 'BACKUP.METADATA'
| 'BACKUP.MIRROR'
| 'BACKUP.WITH_RAM'
| 'BACKUP.SMART_BACKUP'
| 'BACKUP.S3'
| 'DOCKER'
| 'EXPORT.XVA'
| 'LIST_MISSING_PATCHES'
| 'POOL_EMERGENCY_SHUTDOWN'
| 'RBAC'
| 'ROLLING_POOL_UPDATE'
| 'ROLLING_POOL_REBOOT'
| 'SMART_REBOOT'
| 'WARM_MIGRATION'
| 'PLUGIN.OPENMETRICS'
type XapiRecordByXapiXoRecord = {
gpuGroup: XenApiGpuGroupWrapped
host: XenApiHostWrapped
message: XenApiMessageWrapped
network: XenApiNetworkWrapped
PBD: XenApiPbdWrapped
PCI: XenApiPciWrapped
PGPU: XenApiPgpuWrapped
PIF: XenApiPifWrapped
pool: XenApiPoolWrapped
SR: XenApiSrWrapped
SM: XenApiSmWrapped
VBD: XenApiVbdWrapped
VDI: XenApiVdiWrapped
'VDI-snapshot': XenApiVdiWrapped
'VDI-unmanaged': XenApiVdiWrapped
vgpu: XenApiVgpuWrapped
vgpuType: XenApiVgpuTypeWrapped
VIF: XenApiVifWrapped
VM: XenApiVmWrapped
'VM-controller': XenApiVmWrapped
'VM-snapshot': XenApiVmWrapped
'VM-template': XenApiVmWrapped
VTPM: XenApiVtpmWrapped
}
type LicenseProductId =
| 'premium'
| 'xcpng-enterprise'
| 'xcpng-standard'
| 'xo-proxy'
| 'xosan.trial'
| 'xostor'
| 'xostor.trial'
type LicenseProductType = 'xo' | 'xoproxy' | 'xcpng' | 'xosan' | 'xostor'
type License = {
id: string
type: 'license'
created: number
licenseType: 'unit'
productId: LicenseProductId
tags: string[]
expires?: number
boundObjectId?: string
bindDate?: number
buyer?: { token: string; email: string }
history?: { date: number; boundObjectId: string }[]
rebound?: number
productTypes?: LicenseProductType[]
bundleInfo?: { name: string; id: string }
}
export type XoApp = {
hooks: EventEmitter
_redis: {
get(key: string): Promise<string | null>
mSet(args: string[]): Promise<unknown>
sMembers(key: string): Promise<string[]>
sIsMember(key: string, member: string): Promise<boolean>
}
config: {
get<T = string>(path: string): T
getOptional<T = unknown>(path: string): T | undefined
getOptionalDuration(path: string): number | undefined
getGuiRoutes(): Promise<{
default: {
url: string
path: string
}
v5?: {
url: string
path: string
}
v6?: {
url: string
path: string
}
[key: string]:
| undefined
| {
url: string
path: string
}
}>
}
objects: EventEmitter & {
allIndexes: {
type: {
getEventEmitterByType(type: XapiXoRecord['type']): EventEmitter
}
}
}
tasks: EventEmitter & {
abort(id: XoTask['id'], reason?: string): Promise<void>
clearLogs(): Promise<void>
create: (params: { name: string; objectId?: string; type?: string }) => VatesTask
deleteLog(id: XoTask['id']): Promise<void>
get(id: XoTask['id']): Promise<XoTask>
list(opts?: { filter?: string | ((obj: XoTask) => boolean); limit?: number }): AsyncGenerator<XoTask>
watch(id: XoTask['id'], cb: (task: XoTask) => void): Promise<() => void>
}
apiContext: {
user?: XoUser
permission?: XoUser['permission'] | 'none' | null
}
// methods ------------
allocIpAddresses(vifId: XoVif['id'], addAddresses?: string[], removeAddresses?: string[]): Promise<void>
addAclV2GroupRole(groupId: XoGroup['id'], roleId: XoAclRole['id']): Promise<XoGroupRole>
addAclV2UserRole(userId: XoUser['id'], roleId: XoAclRole['id']): Promise<XoUserRole>
addUserToGroup: (userId: XoUser['id'], groupId: XoGroup['id']) => Promise<void>
addApiMethod: <A extends unknown[], R>(
name: string,
method: (...args: A) => Promise<R>,
//we use any since it is a legacy call, might be better typed in another pr
info: {
resolve?: any
params?: any
}
) => () => void // eslint-disable-line @typescript-eslint/no-explicit-any
registerRestRoutes: (routes: PluginRestRouteDefinition[], base?: string) => () => void
authenticateUser: (
credentials: { token?: string; username?: string; password?: string },
userData?: { ip?: string },
opts?: { bypassOtp?: boolean; bypassTaskCreation?: boolean }
) => Promise<{ bypassOtp: boolean; expiration: number; user: XoUser }>
backupGuard(poolId: XoPool['id']): Promise<void>
/* Throw if no authorization */
checkFeatureAuthorization(featureCode: FeatureCode): Promise<void>
/* connect a server (XCP-ng/XenServer) */
connectXenServer(id: XoServer['id']): Promise<void>
// TODO: replace all XoAclBasePrivilege with a more strict type. (discriminate union)
createAclV2Privilege(
privilege: Omit<XoAclBasePrivilege, 'id'>,
options?: {
force?: boolean
}
): Promise<XoAclBasePrivilege>
copyAclV2Role(
id: XoAclRole['id'],
params?: { name?: XoAclRole['name']; description?: XoAclRole['description'] }
): Promise<XoAclRole['id']>
createAclV2Role(role: { name: XoAclRole['name']; description?: XoAclRole['description'] }): Promise<XoAclRole>
createAuthenticationToken(opts: {
client?: {
id?: string
[key: string]: unknown
}
description?: string
expiresIn?: string | number
userId: XoUser['id']
}): Promise<XoAuthenticationToken>
createRemote(params: {
name: string
options?: string
proxy?: XoProxy['id']
url: string
}): Promise<XoBackupRepository>
createUser(params: { name?: string; password?: string; [key: string]: unknown }): Promise<XoUser>
deleteAclV2GroupRole(
groupId: XoGroup['id'],
roleId: XoAclRole['id'],
opts?: { bypassAuthorization?: boolean }
): Promise<boolean>
deleteAclV2Privilege(privilegeId: XoAclBasePrivilege['id'], options?: { force?: boolean }): Promise<boolean>
deleteAclV2Role(roleId: XoAclRole['id'], options?: { force?: boolean }): Promise<boolean>
deleteGroup(id: XoGroup['id']): Promise<void>
deleteUser(id: XoUser['id']): Promise<void>
detachHostFromPool(hostId: XoHost['id']): Promise<void>
createGroup(params: { name: string; provider?: string; providerGroup?: string }): Promise<XoGroup>
/* disconnect a server (XCP-ng/XenServer) */
disconnectXenServer(id: XoServer['id']): Promise<void>
findEnabledScheduleSequenceFromSchedule(id: XoSchedule['id']): Promise<XoSchedule | undefined>
getAclV2Privilege(id: XoAclBasePrivilege['id']): Promise<XoAclBasePrivilege>
getAclV2Privileges(): Promise<XoAclBasePrivilege[]>
getAclV2RolePrivileges(roleId: XoAclRole['id']): Promise<XoAclBasePrivilege[]>
getAclV2Role(id: XoAclRole['id'], opts?: { bypassAuthorization?: boolean }): Promise<XoAclRole>
deleteAclV2UserRole(
userId: XoUser['id'],
roleId: XoAclRole['id'],
opts?: { bypassAuthorization?: boolean }
): Promise<boolean>
getAclV2GroupRoles(
groupId: XoGroup['id'],
opts?: { bypassAuthorization?: boolean }
): Promise<Exclude<XoAclRole, { isTemplate: true }>[]>
getAclV2Roles(): Promise<XoAclRole[]>
getAclV2UserPrivileges(userId: XoUser['id']): Promise<XoAclBasePrivilege[]>
getAclV2UserRoles(
userId: XoUser['id'],
opts?: { bypassAuthorization?: boolean; fromGroup?: boolean; fromUser?: boolean }
): Promise<XoAclRole[]>
getAllGroups(): Promise<XoGroup[]>
getAllProxies(): Promise<XoProxy[]>
getAllJobs<T extends AnyXoJob['type']>(type: T): Promise<Extract<AnyXoJob, { type: T }>[]>
getAllJobs(type?: string): Promise<AnyXoJob[]>
getProxy(id: XoProxy['id']): Promise<XoProxy>
getRemote(id: XoBackupRepository['id']): Promise<XoBackupRepository>
getAllRemotes(): Promise<XoBackupRepository[]>
removeRemote(id: XoBackupRepository['id']): Promise<void>
getAllRemotesInfo(): Promise<
Record<
XoBackupRepository['id'],
{
size?: number
used?: number
available?: number
encryption?: {
algorithm: string
isLegacy: boolean
recommanded: string
}
}
>
>
getAllSchedules(): Promise<XoSchedule[]>
getAllUsers(): Promise<XoUser[]>
getAllXenServers(): Promise<XoServer[]>
getAuthenticationTokensForUser(userId: XoUser['id']): Promise<XoAuthenticationToken[]>
getBackupNgLogs(): Promise<Record<string, AnyXoLog>>
getBackupNgLogs(id: AnyXoLog['id']): Promise<AnyXoLog>
getBackupNgLogsSorted(opts: {
after?: number
before?: number
filter: (log: AnyXoLog) => boolean
limit?: number
}): Promise<AnyXoLog[]>
getGroup(id: XoGroup['id']): Promise<XoGroup>
getHVSupportedVersions: undefined | (() => Promise<{ [key: XoHost['productBrand']]: string }>)
getJob<T extends AnyXoJob>(id: T['id']): Promise<T>
getObject: <T extends XapiXoRecord>(id: T['id'], type?: T['type'] | T['type'][]) => T
getObjectsByType: <T extends XapiXoRecord>(
type: T['type'],
opts?: { filter?: string | ((obj: T) => boolean); limit?: number }
) => Record<T['id'], T> | undefined
getTotalBackupSizeOnRemote(id: XoBackupRepository['id']): Promise<{ onDisk: number }>
getSchedule(id: XoSchedule['id']): Promise<XoSchedule>
getUser: (id: XoUser['id']) => Promise<XoUser>
getXapi(maybeId: XapiXoRecord['id'] | XapiXoRecord): Xapi
getXapiHostStats: (hostId: XoHost['id'], granularity?: XapiStatsGranularity) => Promise<XapiHostStats>
getXapiObject: <T extends XapiXoRecord>(
maybeId: T['id'] | T,
type: T['type'] | T['type'][]
) => XapiRecordByXapiXoRecord[T['type']]
getXapiPoolStats(poolId: XoPool['id'], granularity?: XapiStatsGranularity): Promise<XapiPoolStats>
getXapiVmStats: (vmId: XoVm['id'], granularity?: XapiStatsGranularity) => Promise<XapiVmStats>
getXenServer(id: XoServer['id']): Promise<XoServer>
hasFeatureAuthorization(featureCode: string): Promise<boolean>
hasObject<T extends XapiXoRecord>(id: T['id'], type: T['type']): boolean
listMetadataBackups(backupRepositoryIds: XoBackupRepository['id'][]): Promise<{
xo: Record<XoBackupRepository['id'], XoConfigBackupArchive[]>
pool: Record<XoBackupRepository['id'], Record<XoPool['id'], XoPoolBackupArchive[]>>
}>
listVmBackupsNg(
backupRepositoryIds: XoBackupRepository['id'][],
opts?: { _forceRefresh?: boolean; vmId: XoVm['id'] }
): Promise<Record<XoBackupRepository['id'], Record<XoVm['id'], XoVmBackupArchive[]>>>
pingRemote(id: XoBackupRepository['id']): Promise<{ success: true }>
/** Allow to add a new server in the DB (XCP-ng/XenServer) */
registerXenServer(
body: Pick<XoServer, 'host' | 'httpProxy' | 'label' | 'username'> & {
allowUnauthorized?: XoServer['allowUnauthorized']
password: string
readOnly?: XoServer['readOnly']
}
): Promise<XoServer>
rollingPoolReboot(pool: XoPool, opts?: { parentTask?: VatesTask; shutdownPinnedVms?: boolean }): Promise<void>
rollingPoolUpdate(
pool: XoPool,
opts?: { rebootVm?: boolean; parentTask?: VatesTask; shutdownPinnedVms?: boolean }
): Promise<void>
setVmResourceSet(vmId: XoVm['id'], resourceSetId: string | null, force?: boolean): Promise<void>
shareVmResourceSet(vmId: XoVm['id']): Promise<void>
removeUserFromGroup(userId: XoUser['id'], id: XoGroup['id']): Promise<void>
runJob(job: AnyXoJob, schedule: XoSchedule): void
runWithApiContext: (user: XoUser | undefined, fn: () => void) => Promise<unknown>
testRemote(
id: XoBackupRepository['id']
): Promise<
| { success: true; readRate: number; writeRate: number }
| { success: false; step: string; file: string; error: unknown }
>
/** Remove a server from the DB (XCP-ng/XenServer) */
unregisterXenServer(id: XoServer['id']): Promise<void>
updateUser(
id: XoUser['id'],
updates: {
/**
* @deprecated
*/
email?: string
authProviders?: Record<string, string>
name?: string
password?: string
permission?: string
preferences?: Record<string, string>
}
): Promise<void>
updateAclV2Privilege(
privilegeId: XoAclBasePrivilege['id'],
privilege: XoAclBasePrivilege
): Promise<XoAclBasePrivilege>
updateAclV2Role(
roleId: XoAclRole['id'],
role: {
name?: XoAclRole['name']
description?: XoAclRole['description'] | null
},
options?: {
force?: boolean
}
): Promise<XoAclRole>
updateGroup(
id: XoGroup['id'],
updates: {
name?: string
}
): void
updateRemote(
id: XoBackupRepository['id'],
params: {
enabled?: boolean
name?: string
options?: string | null
proxy?: XoProxy['id'] | null
url?: string
}
): Promise<XoBackupRepository>
getAllXapis(): Record<string, Xapi>
getObjects(opts?: { filter?: Record<string, unknown>; limit?: number }): Record<string, XapiXoRecord>
getLicenses(params?: { productType?: LicenseProductType }): Promise<License[]>
bindLicense(params: { licenseId: string; boundObjectId: string }): Promise<License>
unbindLicense(params: {
productId: LicenseProductId
boundObjectId: string
licenseId: string
data?: Record<string, string>
}): Promise<License>
}

View File

@@ -1,7 +1,6 @@
// Types based on xapi-object-to-xo
import type {
BACKUP_TYPE,
Branded,
DOMAIN_TYPE,
HOST_ALLOWED_OPERATIONS,
@@ -19,6 +18,9 @@ import type {
VM_OPERATIONS,
VM_POWER_STATE,
} from './common.mjs'
import type * as CMType from './lib/complex-matcher.mjs'
import { XoAclPrivilege, XoAclRole, XoAclSupportedActionsByResource } from './lib/xen-orchestra-acl.mjs'
import type { XenApiHost, XenApiPool } from './xen-api.mjs'
type BaseXapiXo = {
$pool: XoPool['id']
@@ -74,7 +76,7 @@ type BaseXoVm = BaseXapiXo & {
expNestedHvm: boolean
isNestedVirtEnabled: boolean
hasVendorDevice: boolean
high_availability: string
high_availability: 'best-effort' | 'restart' | ''
installTime?: number | null
isFirmwareSupported: boolean
memory: {
@@ -92,7 +94,7 @@ type BaseXoVm = BaseXapiXo & {
notes?: string
os_version: Record<string, string> | null
other: Record<string, string>
parent?: XoVm['id']
parent?: XoVmSnapshot['id']
power_state: VM_POWER_STATE
pvDriversDetected?: boolean
pvDriversUpToDate?: boolean
@@ -105,6 +107,10 @@ type BaseXoVm = BaseXapiXo & {
suspendSr?: XoSr['id']
tags: string[]
vga?: string
/**
* Value in MiB.
* See: https://wiki.xenproject.org/wiki/XCP_PV_templates_start
*/
videoram?: number
viridian: boolean
virtualizationMode: DOMAIN_TYPE
@@ -133,6 +139,67 @@ export type XoAlarm = Omit<XoMessage, '$object' | 'body'> & {
}
}
// TODO: to be typed when Bastien.N has finished working on the XO task
type BaseXoLog = {
id: Branded<'xo-log'>
infos?: { data: unknown; message: string }[]
[key: string]: unknown
}
export type XoBackupLog = BaseXoLog & {
status: 'success' | 'skipped' | 'interrupted' | 'failure' | 'pending'
message: 'backup'
start: number
end?: number
tasks?: XoTask[]
jobId?: AnyXoBackupJob['id']
}
export type XoRestoreLog = BaseXoLog & {
message: 'restore'
}
export type XoVmBackupArchive = {
id: Branded<'vm-backup-archive'>
type: 'xo-vm-backup'
backupRepository: XoBackupRepository['id']
disks: { id: string; name: string; uuid: XoVdiSnapshot['uuid'] }[]
isImmutable?: boolean
jobId: XoVmBackupJob['id']
mode: XoVmBackupJob['mode']
scheduleId: XoSchedule['id']
size: number
timestamp: number
vm: {
uuid: XoVm['uuid']
name_description: string
name_label: string
tags: XoVm['tags']
}
differencingVhds?: number
dynamicVhds?: number
withMemory: boolean
}
type XoMetadataBackupArchive = {
id: Branded<'metadata-backup-archive'>
backupRepository: XoBackupRepository['id']
jobId: XoVmBackupJob['id']
jobName: XoVmBackupJob['name']
scheduleId: XoSchedule['id']
scheduleName: XoSchedule['name']
timestamp: number
}
export type XoConfigBackupArchive = XoMetadataBackupArchive & {
id: Branded<'config-backup-archive'>
data: string
type: 'xo-config-backup'
}
export type XoPoolBackupArchive = XoMetadataBackupArchive & {
id: Branded<'pool-backup-archive'>
pool: XenApiPool
poolMaster: XenApiHost
type: 'xo-pool-metadata-backup'
}
export type XoBackupRepository = {
benchmarks?: { readRate: number; timestamp: number; writeRate: number }[]
enabled: boolean
@@ -155,6 +222,7 @@ export type XoGroup = {
provider?: string
providerGroupId?: string
users: XoUser['id'][]
aclRoleIds: XoAclRole['id'][]
}
export type XoHost = BaseXapiXo & {
@@ -173,7 +241,7 @@ export type XoHost = BaseXapiXo & {
address: string
agentStartTime: null | number
bios_string: Record<string, string>
bios_strings: Record<string, string>
build: string
certificates?: {
fingerprint: string
@@ -182,7 +250,7 @@ export type XoHost = BaseXapiXo & {
chipset_info: {
iommu?: boolean
}
controlDomain?: XoVm['id']
controlDomain?: XoVmController['id']
cpus: {
cores?: number
sockets?: number
@@ -268,6 +336,7 @@ export type XoNetwork = BaseXapiXo & {
defaultIsLocked: boolean
id: Branded<'network'>
insecureNbd?: boolean
isBonded: boolean
name_description: string
name_label: string
nbd?: boolean
@@ -277,7 +346,19 @@ export type XoNetwork = BaseXapiXo & {
}
export type XoPbd = BaseXapiXo & {
attached: boolean
device_config:
| { device: string }
| { location: string }
| { path: string; location: string; legacy_mode: string }
| { provisioning: string; redundancy: string; 'group-name': string }
| { server: string; serverpath: string }
| { type: string; location: string }
| Record<string, string>
host: XoHost['id']
id: Branded<'PBD'>
otherConfig: Record<string, string>
SR: XoSr['id']
type: 'PBD'
}
@@ -318,7 +399,7 @@ export type XoPif = BaseXapiXo & {
attached: boolean
bondMaster?: XoPif['id']
bondSalves?: XoPif['id'][]
bondSlaves?: XoPif['id'][]
carrier: boolean
device: string
deviceName?: string
@@ -351,8 +432,9 @@ export type XoPool = BaseXapiXo & {
}
crashDumpSr?: XoSr['id']
current_operations: Record<string, POOL_ALLOWED_OPERATIONS>
defaultSr?: XoSr['id']
default_SR?: XoSr['id']
HA_enabled: boolean
haRebootVmOnInternalShutdown: boolean
haSrs: XoSr['id'][]
id: Branded<'pool'>
master: XoHost['id']
@@ -365,69 +447,144 @@ export type XoPool = BaseXapiXo & {
tags: string[]
type: 'pool'
vtpmSupported: boolean
xosanPackInstallationTime: number | null
zstdSupported: boolean
}
export type XoProxy = {
id: Branded<'proxy'>
}
url: string
version?: string
name: string
} & (
| { address?: undefined; vmUuid: XoVm['id'] }
| { address: string; vmUuid?: undefined }
| { address: string; vmUuid: XoVm['id'] }
)
type BaseXoJob = {
id: Branded<'job'>
name?: string
}
// @TODO: create type for complex matcher
export type XoBackupJob = BaseXoJob & {
type XoBackupJobGeneralSettings = {
backupReportTpl?: 'compactMjml' | 'mjml'
reportWhen?: 'always' | 'error' | 'failure' | 'never'
reportRecipients?: string[]
hideSuccessfulItems?: boolean
[key: string]: unknown
}
export type XoVmBackupJobGeneralSettings = XoBackupJobGeneralSettings & {
cbtDestroySnapshotData?: boolean
concurrency?: number
longTermRetention?: {
daily?: { retention: number; settings: Record<string, unknown> }
weekly?: { retention: number; settings: Record<string, unknown> }
monthly?: { retention: number; settings: Record<string, unknown> }
yearly?: { retention: number; settings: Record<string, unknown> }
}
checkpointSnapshot?: boolean
offlineSnapshot?: boolean
maxExportRate?: number
nbdConcurrency?: number
nRetriesVmBackupFailures?: number
preferNbd?: boolean
timezone?: string
mergeBackupsSynchronously?: boolean
offlineBackup?: boolean
timeout?: number
}
export type XoVmBackupJobScheduleSettings = {
exportRetention?: number
healthCheckVmsWithTags?: XoVm['tags']
fullInterval?: number
copyRetention?: number
snapshotRetention?: number
cbtDestroySnapshotData?: boolean
healthCheckSr?: XoSr['id']
[key: string]: unknown
}
export type XoVmBackupJob = BaseXoJob & {
compression?: 'native' | 'zstd' | ''
proxy?: XoProxy['id']
mode: 'full' | 'delta'
name?: string
remotes?: {
id: XoBackupRepository['id'] | { __or: XoBackupRepository['id'][] }
}
vms?: {
id: XoVm['id'] | { __or: XoVm['id'][] } | Record<string, unknown>
}
srs: {
id: XoSr['id'] | { __or: XoSr['id'][] }
}
type: BACKUP_TYPE
remotes?: CMType.IdOr<XoBackupRepository['id']>
vms: CMType.IdOr<XoVm['id']> | Record<string, unknown>
srs?: CMType.IdOr<XoSr['id']>
type: 'backup'
settings: {
'': {
cbtDestroySnapshotData?: boolean
concurrency?: number
longTermRetention?: {
daily?: { retention: number; settings: Record<string, unknown> }
weekly?: { retention: number; settings: Record<string, unknown> }
monthly?: { retention: number; settings: Record<string, unknown> }
yearly?: { retention: number; settings: Record<string, unknown> }
}
maxExportRate?: number
nbdConcurrency?: number
nRetriesVmBackupFailures?: number
preferNbd?: boolean
timezone?: string
[key: string]: unknown
}
[key: XoSchedule['id']]: {
exportRetention?: number
healthCheckSr?: XoSr['id']
healthCheckVmsWithTags?: string[]
fullInterval?: number
copyRetention?: number
snapshotRetention?: number
cbtDestroySnapshotData?: boolean
[key: string]: unknown
}
'': XoVmBackupJobGeneralSettings
[key: XoSchedule['id']]: XoVmBackupJobScheduleSettings | undefined
}
}
export type XoMetadataBackupJobGeneralSettings = XoBackupJobGeneralSettings
export type XoMetadataBackupJobScheduleSettings = {
retentionPoolMetadata?: number
retentionXoMetadata?: number
[key: string]: unknown
}
export type XoMetadataBackupJob = BaseXoJob & {
type: 'metadataBackup'
pools?: CMType.IdOr<XoPool['id']>
remotes: CMType.IdOr<XoBackupRepository['id']>
settings: {
''?: XoMetadataBackupJobGeneralSettings
[scheduleId: XoSchedule['id']]: XoMetadataBackupJobScheduleSettings | undefined
}
xoMetadata?: boolean
userId: XoUser['id']
proxy?: XoProxy['id']
}
export type XoMirrorBackupGeneralSettings = XoBackupJobGeneralSettings & {
concurrency?: number
nRetriesVmBackupFailures?: number
mergeBackupsSynchronously?: boolean
timeout?: number
maxExportRate?: number
backupReportTpl?: 'compactMjml'
reportWhen: 'failure'
}
export type XoMirrorBackupScheduleSettings = {
exportRetention?: number
healthCheckVmsWithTags?: XoVm['tags']
healthCheckSr?: XoSr['id']
[key: string]: unknown
}
export type XoMirrorBackupJob = BaseXoJob & {
type: 'mirrorBackup'
mode: 'full' | 'delta'
sourceRemote: XoBackupRepository['id']
remotes: CMType.IdOr<XoBackupRepository['id']>
proxy?: XoProxy['id']
settings: {
'': XoMirrorBackupGeneralSettings
[scheduleId: XoSchedule['id']]: XoMirrorBackupScheduleSettings | undefined
}
}
export type XoJob = BaseXoJob & {
type: 'call'
method: string
paramsVector?: {
type: string
items: {
type?: string
values?: {
schedules?: XoSchedule['id'][]
[key: string]: unknown
}[]
[key: string]: unknown
}[]
}
}
export type XoJob = BaseXoJob & {}
export type XoSchedule = {
cron: string
enabled: boolean
id: Branded<'schedule'>
jobId: (XoJob | XoBackupJob)['id']
jobId: AnyXoJob['id']
name?: string
timezone?: string
}
@@ -456,8 +613,8 @@ export type XoSr = BaseXapiXo & {
VDIs: AnyXoVdi['id'][]
allocationStrategy: 'thin' | 'thick' | 'unknown'
content_type: string
allocationStrategy?: 'thin' | 'thick' | 'unknown'
content_type: 'user' | 'iso' | 'disk'
current_operations: Record<string, STORAGE_OPERATIONS>
id: Branded<'SR'>
inMaintenanceMode: boolean
@@ -468,7 +625,28 @@ export type XoSr = BaseXapiXo & {
shared: boolean
size: number
sm_config: Record<string, string>
SR_type: string
// SM drivers shipped with XCP-ng/XenServer
SR_type:
| 'cephfs'
| 'dummy'
| 'ext'
| 'file'
| 'glusterfs'
| 'hba'
| 'iso'
| 'largeblock'
| 'linstor'
| 'lvm'
| 'lvmofcoe'
| 'lvmohba'
| 'lvmoiscsi'
| 'moosefs'
| 'nfs'
| 'shm'
| 'smb'
| 'udev'
| 'xfs'
| 'zfs'
tags: string[]
type: 'SR'
usage: number
@@ -489,17 +667,72 @@ export type XoSm = BaseXapiXo & {
type: 'SM'
}
export type XoTask = {
abortionRequestedAt?: number
data?: Record<string, string>
end?: number
id: Branded<'task'>
infos?: { data: unknown; message: string }[]
progress?: number
properties: {
method?: string
name?: string
objectId?: string
objectType?:
| XapiXoRecord['type']
| 'backup'
| 'backup-archive'
| 'backup-job'
| 'backup-log'
| 'backup-repository'
| 'group'
| 'proxy'
| 'restore'
| 'restore-log'
| 'schedule'
| 'server'
| 'task'
| 'user'
| 'acl-privilege'
| 'acl-role'
params?: Record<string, unknown>
progress?: number
type?: string
userId?: string
[key: string]: unknown | undefined
}
result?: Record<string, unknown>
start: number
status: 'failure' | 'interrupted' | 'pending' | 'success'
tasks?: XoTask[]
updatedAt?: number
warnings?: { data: unknown; message: string }[]
}
export type XoUser = {
authProviders?: Record<string, string>
email: string
groups: XoGroup['id'][]
id: Branded<'user'>
name?: string
permission: string
permission: 'none' | 'admin'
pw_hash?: string
preferences: Record<string, string>
}
export type XoAuthenticationToken = {
client?: {
id: string
[key: string]: unknown
}
created_at?: number
description?: string
user_id: XoUser['id']
expiration: number
last_uses?: Record<string, { timestamp: number }>
id: Branded<'authentication-token'>
}
export type XoVbd = BaseXapiXo & {
attached: boolean
bootable: boolean
@@ -535,6 +768,7 @@ type BaseXoVdi = BaseXapiXo & {
export type XoVdi = BaseXoVdi & {
id: Branded<'VDI'>
chainPhysicalUsage: number
type: 'VDI'
}
@@ -635,6 +869,7 @@ export type XapiXoRecord =
| XoHost
| XoMessage
| XoNetwork
| XoPbd
| XoPci
| XoPgpu
| XoPif
@@ -654,12 +889,37 @@ export type XapiXoRecord =
| XoVtpm
| XoSm
export type NonXapiXoRecord = XoGroup | XoProxy | XoJob | XoBackupRepository | XoSchedule | XoServer | XoUser
export type NonXapiXoRecord<
TActionsByResource extends XoAclSupportedActionsByResource = never,
TResource extends string = never,
> =
| AnyXoBackupArchive
| AnyXoJob
| AnyXoLog
| XoGroup
| XoProxy
| XoJob
| XoBackupRepository
| XoSchedule
| XoServer
| XoTask
| XoUser
| XoAclRole
| XoAclPrivilege<TActionsByResource, TResource>
export type XoRecord = XapiXoRecord | NonXapiXoRecord
export type XoRecord<
TActionsByResource extends XoAclSupportedActionsByResource = never,
TResource extends string = never,
> = XapiXoRecord | NonXapiXoRecord<TActionsByResource, TResource>
export type AnyXoVm = XoVm | XoVmSnapshot | XoVmTemplate | XoVmController
export type AnyXoVdi = XoVdi | XoVdiSnapshot | XoVdiUnmanaged
export type AnyXoJob = XoJob | XoBackupJob
export type AnyXoJob = XoJob | AnyXoBackupJob
export type AnyXoBackupJob = XoVmBackupJob | XoMetadataBackupJob | XoMirrorBackupJob
export type AnyXoLog = XoBackupLog | XoRestoreLog
export type AnyXoBackupArchive = XoVmBackupArchive | XoConfigBackupArchive | XoPoolBackupArchive

View File

@@ -5,7 +5,8 @@
"strict": true,
"module": "ESNext",
"target": "ES2022",
"skipLibCheck": true
"skipLibCheck": true,
"types": ["node"] // Only use types from Node.js and packages that are explicitly imported
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]

View File

@@ -0,0 +1 @@
../../scripts/npmignore

View File

@@ -0,0 +1,16 @@
// @ts-check
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export default tseslint.config(eslint.configs.recommended, tseslint.configs.recommended, {
languageOptions: {
parserOptions: {
tsconfigRootDir: __dirname,
},
}
})

View File

@@ -0,0 +1,47 @@
{
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"main": "dist/index.mjs",
"name": "@xen-orchestra/acl",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/acl",
"version": "1.5.0",
"license": "AGPL-3.0-or-later",
"private": false,
"type": "module",
"engines": {
"node": ">= 20.18"
},
"exports": {
".": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
}
},
"scripts": {
"build": "tsc && node scripts/generate-types.mjs",
"clean": "rimraf dist/",
"dev": "tsc --watch",
"postversion": "npm publish --access public",
"test": "npm run build && node --test ./dist/**/*.test.mjs",
"prebuild": "npm run clean",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
"@vates/types": "^1.30.1",
"rimraf": "^6.0.1",
"typescript": "~5.6",
"typescript-eslint": "^8.61.0"
},
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@xen-orchestra/acl",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"dependencies": {
"complex-matcher": "^1.0.0"
}
}

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env node
// AUTO-GENERATES dist/generated/privilege-types.d.mts
// Run via `npm run build` (after tsc).
import { mkdirSync, writeFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { SUPPORTED_ACTIONS_BY_RESOURCE } from '../dist/actions/index.mjs'
import { getActionStrings } from '../dist/index.mjs'
function toPascalCase(str) {
return str
.split(/[-_]/)
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
.join('')
}
const resources = Object.keys(SUPPORTED_ACTIONS_BY_RESOURCE)
const typeBlocks = resources.map(resource => {
const actionUnion = getActionStrings(resource)
.map(action => `'${action}'`)
.join(' | ')
return `export type ${toPascalCase(resource)}Privilege = {
id: XoAclBasePrivilege['id']
resource: '${resource}'
action: ${actionUnion}
selector?: XoAclBasePrivilege['selector']
effect: XoAclBasePrivilege['effect']
roleId: XoAclBasePrivilege['roleId']
}`
})
const anyUnion = resources.map(resource => ` | ${toPascalCase(resource)}Privilege`).join('\n')
const types = `\
// -----------------------------------------------------------------------
// AUTO-GENERATED — do not edit manually.
// Regenerated on every \`npm run build\`.
// -----------------------------------------------------------------------
import type { XoAclBasePrivilege } from '@vates/types'
${typeBlocks.join('\n\n')}
export type AnyPrivilege =
${anyUnion}
`
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
const outDir = join(root, 'dist/generated')
mkdirSync(outDir, { recursive: true })
writeFileSync(join(outDir, 'privilege-types.d.mts'), types, 'utf-8')

View File

@@ -0,0 +1,11 @@
export default {
create: true,
delete: true,
read: true,
update: {
action: true,
effect: true,
resource: true,
selector: true,
},
}

View File

@@ -0,0 +1,11 @@
export default {
create: true,
delete: true,
read: true,
update: {
description: true,
groups: true,
name: true,
users: true,
},
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,13 @@
export default {
benchmark: true,
create: true,
read: true,
forget: true,
update: {
enabled: true,
name: true,
options: true,
proxy: true,
url: true,
},
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,9 @@
export default {
create: true,
delete: true,
read: true,
update: {
name: true,
users: true,
},
}

View File

@@ -0,0 +1,28 @@
export default {
'allow-vm': true,
detach: true,
disable: true,
enable: true,
evacuate: true,
export: {
logs: true,
},
forget: true,
'join-pool': true,
'migrate-receive': true,
read: true,
reboot: {
clean: true,
smart: true,
},
'restart-toolstack': true,
'scan-pifs': true,
shutdown: {
clean: true,
emergency: true,
},
start: true,
update: {
tags: true,
},
}

View File

@@ -0,0 +1,80 @@
import { default as aclPrivilege } from './acl-privilege.mjs'
import { default as aclRole } from './acl-role.mjs'
import { default as alarm } from './alarm.mjs'
import { default as backupArchive } from './backup-archive.mjs'
import { default as backupJob } from './backup-job.mjs'
import { default as backupLog } from './backup-log.mjs'
import { default as backupRepository } from './backup-repository.mjs'
import { default as gpuGroup } from './gpuGroup.mjs'
import { default as group } from './group.mjs'
import { default as host } from './host.mjs'
import { default as message } from './message.mjs'
import { default as network } from './network.mjs'
import { default as pbd } from './pbd.mjs'
import { default as pci } from './pci.mjs'
import { default as pgpu } from './pgpu.mjs'
import { default as pif } from './pif.mjs'
import { default as pool } from './pool.mjs'
import { default as proxy } from './proxy.mjs'
import { default as restoreLog } from './restore-log.mjs'
import { default as schedule } from './schedule.mjs'
import { default as server } from './server.mjs'
import { default as sm } from './sm.mjs'
import { default as sr } from './sr.mjs'
import { default as task } from './task.mjs'
import { default as user } from './user.mjs'
import { default as vbd } from './vbd.mjs'
import { default as vdiSnapshot } from './vdi-snapshot.mjs'
import { default as vdiUnmanaged } from './vdi-unmanaged.mjs'
import { default as vdi } from './vdi.mjs'
import { default as vgpu } from './vgpu.mjs'
import { default as vgpuType } from './vgpuType.mjs'
import { default as vif } from './vif.mjs'
import { default as vmController } from './vm-controller.mjs'
import { default as vmSnapshot } from './vm-snapshot.mjs'
import { default as vmTemplate } from './vm-template.mjs'
import { default as vm } from './vm.mjs'
import { default as vtpm } from './vtpm.mjs'
/**
* value without sub action like 'shutdown' mean: 'shutdown:*' (so shutdown mean you can shutdown:clean and shutdown:hard)
*/
export const SUPPORTED_ACTIONS_BY_RESOURCE = {
'acl-privilege': aclPrivilege,
'acl-role': aclRole,
alarm,
'backup-archive': backupArchive,
'backup-job': backupJob,
'backup-log': backupLog,
'backup-repository': backupRepository,
group,
gpuGroup,
host,
message,
network,
pbd,
pci,
pgpu,
pif,
pool,
proxy,
'restore-log': restoreLog,
schedule,
server,
sm,
sr,
task,
user,
vbd,
'vdi-snapshot': vdiSnapshot,
'vdi-unmanaged': vdiUnmanaged,
vdi,
vgpu,
vgpuType,
vif,
'vm-controller': vmController,
'vm-snapshot': vmSnapshot,
'vm-template': vmTemplate,
vm,
vtpm,
} as const

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,9 @@
export default {
create: true,
delete: true,
read: true,
update: {
tags: true,
other_config: true,
},
}

View File

@@ -0,0 +1,5 @@
export default {
read: true,
plug: true,
unplug: true,
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,6 @@
export default {
read: true,
update: {
management: true,
},
}

View File

@@ -0,0 +1,14 @@
export default {
'add-host': true,
create: {
network: true,
vm: true,
},
'emergency-shutdown': true,
read: true,
'rolling-reboot': true,
'rolling-update': true,
update: {
tags: true,
},
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,4 @@
export default {
read: true,
run: true,
}

View File

@@ -0,0 +1,7 @@
export default {
connect: true,
create: true,
delete: true,
disconnect: true,
read: true,
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,16 @@
export default {
create: true,
delete: true,
forget: true,
import: {
vdi: true,
vm: true,
},
'migrate-receive': true,
read: true,
'reclaim-space': true,
scan: true,
update: {
tags: true,
},
}

View File

@@ -0,0 +1,5 @@
export default {
abort: true,
delete: true,
read: true,
}

View File

@@ -0,0 +1,11 @@
export default {
create: true,
delete: true,
read: true,
update: {
name: true,
password: true,
permission: true,
preferences: true,
},
}

View File

@@ -0,0 +1,7 @@
export default {
connect: true,
create: true,
delete: true,
disconnect: true,
read: true,
}

View File

@@ -0,0 +1,8 @@
export default {
delete: true,
export: true,
read: true,
update: {
tags: true,
},
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,15 @@
export default {
boot: true,
create: true,
delete: true,
'export-content': true,
'import-content': true,
'migrate-send': true,
read: true,
update: {
name_description: true,
name_label: true,
size: true,
tags: true,
},
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,15 @@
export default {
connect: true,
create: true,
delete: true,
disconnect: true,
read: true,
update: {
allowedIpv4Addresses: true,
allowedIpv6Addresses: true,
lockingMode: true,
rateLimit: true,
txChecksumming: true,
other_config: true,
},
}

View File

@@ -0,0 +1,6 @@
export default {
read: true,
update: {
tags: true,
},
}

View File

@@ -0,0 +1,8 @@
export default {
delete: true,
export: true,
read: true,
update: {
tags: true,
},
}

View File

@@ -0,0 +1,9 @@
export default {
delete: true,
export: true,
instantiate: true,
read: true,
update: {
tags: true,
},
}

View File

@@ -0,0 +1,61 @@
export default {
clone: true,
delete: true,
export: true,
'migrate-send': true,
pause: true,
read: true,
reboot: {
clean: true,
hard: true,
},
resume: true,
'revert-snapshot': true,
shutdown: {
clean: true,
hard: true,
},
snapshot: true,
start: true,
suspend: true,
unpause: true,
update: {
affinityHost: true,
autoPoweron: true,
blockedOperations: true,
coresPerSocket: true,
cpuCap: true,
cpuMask: true,
cpuWeight: true,
cpus: true,
cpusStaticMax: true,
creation: true,
datasources: true,
expNestedHvm: true,
hasVendorDevice: true,
highAvailability: true,
hvmBootFirmware: true,
memory: true,
memoryMax: true,
memoryMin: true,
memoryStaticMax: true,
nameDescription: true,
nameLabel: true,
nestedVirt: true,
nicType: true,
notes: true,
PV_args: true,
resourceSet: true,
secureBoot: true,
share: true,
startDelay: true,
suspendSr: true,
tags: true,
uefiMode: true,
vga: true,
videoram: true,
viridian: true,
virtualizationMode: true,
xenStoreData: true,
},
}

View File

@@ -0,0 +1,3 @@
export default {
read: true,
}

View File

@@ -0,0 +1,119 @@
import * as CM from 'complex-matcher'
import type { XoAclPrivilege, XoAclSupportedActions, XoAclSupportedResource } from '@vates/types/lib/xen-orchestra/acl'
import { SUPPORTED_ACTIONS_BY_RESOURCE } from '../actions/index.mjs'
export type SupportedActionsByResource = typeof SUPPORTED_ACTIONS_BY_RESOURCE
export type SupportedResource = XoAclSupportedResource<SupportedActionsByResource>
export type SupportedActions<T extends SupportedResource> = XoAclSupportedActions<SupportedActionsByResource, T>
export type TPrivilege<T extends SupportedResource> = XoAclPrivilege<SupportedActionsByResource, T>
export class Privilege<T extends SupportedResource> {
#action: TPrivilege<T>['action']
#selector?: (object: object) => boolean
#resource: TPrivilege<T>['resource']
#effect: TPrivilege<T>['effect']
constructor({
action,
selector,
resource,
effect,
}: {
action: TPrivilege<T>['action']
selector?: TPrivilege<T>['selector']
resource: TPrivilege<T>['resource']
effect: TPrivilege<T>['effect']
}) {
Privilege.checkActionIsValid(resource, action)
this.#action = action
this.#selector = selector !== undefined ? CM.parse(selector).createPredicate() : undefined
this.#resource = resource
this.#effect = effect
}
get effect() {
return this.#effect
}
#matchAction<Resource extends SupportedResource = T>(action: SupportedActions<Resource>) {
// read:name_label - read:name_label
if (this.#action === action) {
return true
}
const [namespaceToMatch, actionToMatch] = action.split(':')
const [thisNamespace, thisAction] = this.#action.split(':')
if (thisNamespace === '*') {
return true
}
// update:vm - read:name_label
if (thisNamespace !== namespaceToMatch) {
return false
}
// read - read:name_label
if (thisAction === undefined) {
return true
}
// read:name_decription - read:name_label
if (thisAction !== actionToMatch) {
return false
}
throw new Error(`Unable to verify if ${this.#action} match ${action} `)
}
#matchSelector(object: object) {
if (this.#selector === undefined) {
return true
}
return this.#selector(object)
}
#matchResource(resource: string): resource is SupportedResource {
return resource === this.#resource
}
match<Resource extends SupportedResource = T>(constraint: {
action: SupportedActions<Resource>
resource: Resource
object: object
}): boolean {
return (
this.#matchResource(constraint.resource) &&
this.#matchAction(constraint.action) &&
this.#matchSelector(constraint.object)
)
}
/**
* Throw if action not supported
*/
static checkActionIsValid<T extends SupportedResource>(resource: T, action: SupportedActions<T>) {
const supportedActions = SUPPORTED_ACTIONS_BY_RESOURCE[resource]
if (supportedActions === undefined) {
throw new Error(`${resource} resource not supported`)
}
if (action === '*') {
return
}
const segments = action.split(':')
let _action: object | undefined = undefined
segments.forEach(segment => {
_action = (_action ?? supportedActions)[segment]
if (_action === undefined) {
throw new Error(
`${action} action not supported for the resource: ${resource}. See ${JSON.stringify(supportedActions)}`
)
}
})
}
}

View File

@@ -0,0 +1,10 @@
// DO NOT EDIT
// Overwritten by scripts/generate-types.mjs after running npm run build
// scripts/generate-types.mjs will replace this file to automatically generate and export all privileged types.
// This makes external use easier because it's not necessary to resolve the type every time.
import type { Privilege, SupportedResource } from '../index.mjs'
export type AnyPrivilege = {
[Resource in SupportedResource]: Privilege<Resource>
}[SupportedResource]

View File

@@ -0,0 +1,167 @@
import assert from 'node:assert'
import type { XoUser } from '@vates/types/xo'
import { SUPPORTED_ACTIONS_BY_RESOURCE } from './actions/index.mjs'
import {
Privilege as CPrivilege,
TPrivilege as Privilege,
SupportedActions,
SupportedActionsByResource,
SupportedResource,
} from './class/privilege.mjs'
// Export all types constructed using SUPPORTED_ACTIONS_BY_RESOURCE
export type { Privilege, SupportedActions, SupportedActionsByResource, SupportedResource }
export { SUPPORTED_ACTIONS_BY_RESOURCE }
export type * from './generated/privilege-types.mjs'
import { AnyPrivilege } from './generated/privilege-types.mjs'
export type AnyPrivilegeOnParam = {
[Resource in SupportedResource]: {
user: XoUser
resource: Resource
action: SupportedActions<Resource>
objects: object | object[]
}
}[SupportedResource]
export function hasPrivilegeOn<T extends SupportedResource>({
user,
action,
resource,
objects,
userPrivileges,
}: {
user: XoUser
resource: T
action: SupportedActions<T>
objects: object | object[]
userPrivileges: AnyPrivilege[]
}) {
// Function that will be called outside of the module
// We cannot be sure types are respected
assert.strictEqual(typeof user?.permission, 'string')
assert.strictEqual(typeof action, 'string')
assert.strictEqual(typeof resource, 'string')
assert.strictEqual(objects === undefined, false)
CPrivilege.checkActionIsValid(resource, action as SupportedActions<typeof resource>)
if (user.permission === 'admin') {
return true
}
const effectsByObject = new Map<object, Privilege<T>['effect'][]>()
if (Array.isArray(objects)) {
objects.forEach(obj => effectsByObject.set(obj, []))
} else {
effectsByObject.set(objects, [])
}
for (const userPrivilege of userPrivileges) {
const privilege = new CPrivilege(userPrivilege as Privilege<typeof userPrivilege.resource>)
for (const [object, effects] of effectsByObject.entries()) {
if (!privilege.match({ action, resource, object })) {
continue
}
effects.push(privilege.effect)
}
}
if (
Array.from(effectsByObject.values()).some(
effects => effects.length === 0 || effects.some(effect => effect === 'deny')
)
) {
return false
}
return true
}
export function getMissingPrivileges(params: AnyPrivilegeOnParam[], userPrivileges: AnyPrivilege[]) {
return params
.filter(
param =>
!hasPrivilegeOn({
user: param.user,
resource: param.resource,
action: param.action as SupportedActions<typeof param.resource>,
objects: param.objects,
userPrivileges,
})
)
.map(({ action, objects, resource }) => {
let objectIds: unknown[] | undefined
let objectId: unknown | undefined
if (Array.isArray(objects)) {
if (objects.length > 1) {
objectIds = []
objects.forEach(obj => {
if ('id' in obj) {
objectIds!.push(obj.id)
}
})
} else {
objects = objects?.[0] ?? {}
}
}
if (!Array.isArray(objects)) {
objectId = 'id' in objects ? objects.id : undefined
}
return {
objectId,
objectIds,
action,
resource,
}
})
}
export function hasPrivileges(params: AnyPrivilegeOnParam[], userPrivileges: AnyPrivilege[]) {
return getMissingPrivileges(params, userPrivileges).length === 0
}
export function filterObjectsWithPrivilege<Resource extends SupportedResource, Object extends object>(param: {
user: XoUser
resource: Resource
action: SupportedActions<Resource>
objects: Object[]
userPrivileges: AnyPrivilege[]
}) {
return param.objects.filter(obj => hasPrivilegeOn({ ...param, objects: obj }))
}
// Utils
/**
* Mirror GetKeysRecursively from @vates/types
*/
export function getActionStrings<Resource extends SupportedResource, Actions = SupportedActions<Resource>>(
resource: Resource
): Actions[] {
type NestedActions = { [key: string]: boolean | NestedActions }
function collectActions(obj: NestedActions, prefix?: Actions): Actions[] {
const actions: Actions[] = ['*' as Actions]
for (const [key, value] of Object.entries(obj)) {
const action = (prefix !== undefined ? `${prefix}:${key}` : key) as Actions
actions.push(action)
if (typeof value === 'boolean') {
continue
}
actions.push(...collectActions(value, action))
}
return actions
}
return collectActions(SUPPORTED_ACTIONS_BY_RESOURCE[resource])
}

View File

@@ -0,0 +1,378 @@
import { test, suite } from 'node:test'
import assert from 'node:assert'
import type { Branded } from '@vates/types/common'
import type { XoUser } from '@vates/types/xo'
import { Privilege } from '../class/privilege.mjs'
import type { Privilege as TPrivilege } from '../index.mjs'
import {
filterObjectsWithPrivilege,
getActionStrings,
getMissingPrivileges,
hasPrivilegeOn,
hasPrivileges,
} from '../index.mjs'
suite('Privilege.checkActionIsValid behavior', () => {
test('throw on invalid resource', () => {
// @ts-expect-error test invalid action
assert.throws(() => Privilege.checkActionIsValid('foo', 'read'))
})
test('throw on invalid action', () => {
// @ts-expect-error test invalid action
assert.throws(() => Privilege.checkActionIsValid('vm', 'foo:vm'))
})
test('should work', () => {
assert.doesNotThrow(() => Privilege.checkActionIsValid('vm', 'read'))
})
})
suite('Privilege.match behavior', () => {
const privilege = new Privilege({ action: 'read', resource: 'vm', effect: 'allow' })
const object = { id: 1, name_label: 'foo' }
test('Should not match because of the action', () => {
assert.strictEqual(privilege.match({ resource: 'vm', action: 'start', object }), false)
})
test('Should not match because of the resource', () => {
assert.strictEqual(privilege.match({ resource: 'host', action: 'read', object }), false)
})
test('Should match', () => {
assert.strictEqual(privilege.match({ resource: 'vm', action: 'read', object }), true)
})
test('Should match even if the action is not equal to the action privilege', () => {
const shutdownPrivilege = new Privilege({ action: 'shutdown', resource: 'vm', effect: 'allow' })
assert.strictEqual(shutdownPrivilege.match({ resource: 'vm', action: 'shutdown:clean', object }), true)
})
test('Should match because the action is *', () => {
const vmPrivilege = new Privilege({ action: '*', resource: 'vm', effect: 'allow' })
assert.strictEqual(vmPrivilege.match({ action: 'reboot:hard', resource: 'vm', object: object }), true)
})
test('Should not match due to insufficient privilege', () => {
const cleanShutdownPrivilege = new Privilege({ action: 'shutdown:clean', resource: 'vm', effect: 'allow' })
assert.strictEqual(cleanShutdownPrivilege.match({ resource: 'vm', action: 'shutdown', object }), false)
})
})
suite('ACL V2 behavior', async () => {
// === Setup ===
const admin = {
id: '1' as XoUser['id'],
groups: [],
permission: 'admin',
email: 'admin@admin.net',
preferences: {},
} satisfies XoUser
const user = {
id: '2' as XoUser['id'],
groups: [],
permission: 'none',
email: 'user@user.net',
preferences: {},
} satisfies XoUser
const privilegeId = 'fake-priv-test-id' as Branded<'acl-v2-privilege'>
const roleId = 'fake-role-test-id' as Branded<'acl-v2-role'>
// === Objects
const poolWithoutPrivilege = { id: 'pool-id' }
const alpineVm = { id: 'alpine', creation: { creator: admin.id } }
const almaVm = { id: 'alma', creation: { creator: user.id } }
const xoaVm = { id: 'xoa' }
const template = { id: 'template-id', $pool: '1' }
const templateDeny = { id: 'template-deny-id', $pool: '2' }
const vdis = [{ $SR: '2' }, { $SR: '2' }]
const vdisMultipleSrs = [{ $SR: '1' }, { $SR: '2' }]
const vifs = [{ $network: '1' }]
const iso = { id: 'iso-vdi-id', $SR: '3' }
const hostAffinity = { id: 'affinity-host-id', $pool: '1' }
// === DB Privileges
const vmRead = { id: privilegeId, action: 'read', resource: 'vm', effect: 'allow', roleId } satisfies TPrivilege<'vm'>
const vmCleanShutdown = {
id: privilegeId,
action: 'shutdown:clean',
resource: 'vm',
selector: `creation:creator:${user.id}`,
effect: 'allow',
roleId,
} satisfies TPrivilege<'vm'>
const xoaVmDenyRead = {
id: privilegeId,
action: 'read',
resource: 'vm',
effect: 'deny',
selector: `id: ${xoaVm.id}`,
roleId,
} satisfies TPrivilege<'vm'>
const vdiCreate = {
id: privilegeId,
action: 'create',
resource: 'vdi',
selector: '$SR:2',
effect: 'allow',
roleId,
} satisfies TPrivilege<'vdi'>
const vifCreate = {
id: privilegeId,
action: 'create',
resource: 'vif',
effect: 'allow',
roleId,
} satisfies TPrivilege<'vif'>
const templateInstantiate = {
id: privilegeId,
action: 'instantiate',
resource: 'vm-template',
effect: 'allow',
roleId,
} satisfies TPrivilege<'vm-template'>
const templateInstantiateDeny = {
id: privilegeId,
action: 'instantiate',
resource: 'vm-template',
selector: '$pool:2',
effect: 'deny',
roleId,
} satisfies TPrivilege<'vm-template'>
const isoVdiUse = {
id: privilegeId,
action: 'boot',
resource: 'vdi',
selector: '$SR:3',
effect: 'allow',
roleId,
} satisfies TPrivilege<'vdi'>
const hostVm = {
id: privilegeId,
action: 'allow-vm',
resource: 'host',
effect: 'allow',
roleId,
} satisfies TPrivilege<'host'>
const allPrivileges = [
vmRead,
vmCleanShutdown,
xoaVmDenyRead,
vdiCreate,
vifCreate,
templateInstantiate,
templateInstantiateDeny,
isoVdiUse,
hostVm,
]
// === Test ===
suite('hasPrivilegeOn behavior', () => {
test('Should always return true for admin', () => {
assert.strictEqual(
hasPrivilegeOn<'pool'>({
user: admin,
action: 'read',
resource: 'pool',
objects: poolWithoutPrivilege,
userPrivileges: allPrivileges,
}),
true
)
})
test('Should return false by default for non admin', () => {
assert.strictEqual(
hasPrivilegeOn({
user,
action: 'read',
resource: 'pool',
objects: poolWithoutPrivilege,
userPrivileges: allPrivileges,
}),
false
)
})
test('Should return true if user has privilege', () => {
assert.strictEqual(
hasPrivilegeOn({ user, action: 'read', resource: 'vm', objects: alpineVm, userPrivileges: allPrivileges }),
true
)
})
test('Should return false if user have a both deny and allow privileges', () => {
assert.strictEqual(
hasPrivilegeOn({ user, action: 'read', resource: 'vm', objects: xoaVm, userPrivileges: allPrivileges }),
false
)
})
test('Should handle multiple and single object', () => {
assert.strictEqual(
hasPrivilegeOn({
user,
action: 'read',
resource: 'vm',
objects: [alpineVm, almaVm],
userPrivileges: allPrivileges,
}),
true
)
assert.strictEqual(
hasPrivilegeOn({
user,
action: 'read',
resource: 'vm',
objects: [alpineVm, almaVm, xoaVm],
userPrivileges: allPrivileges,
}),
false
)
})
test('Should allow clean shutdown only own VMs', () => {
assert.strictEqual(
hasPrivilegeOn({
user,
action: 'shutdown:clean',
resource: 'vm',
objects: almaVm,
userPrivileges: allPrivileges,
}),
true
)
assert.strictEqual(
hasPrivilegeOn({
user,
action: 'shutdown:clean',
resource: 'vm',
objects: [almaVm, alpineVm],
userPrivileges: allPrivileges,
}),
false
)
})
})
suite('getMissingPrivileges behavior', () => {
test('Should return empty array if no missing privileges', () => {
const missingPrivileges = getMissingPrivileges(
[
{ user, action: 'shutdown:clean', resource: 'vm', objects: almaVm },
{ user, action: 'instantiate', resource: 'vm-template', objects: template },
{ user, action: 'create', resource: 'vdi', objects: vdis },
{ user, action: 'create', resource: 'vif', objects: vifs },
{ user, action: 'boot', resource: 'vdi', objects: iso },
{ user, action: 'allow-vm', resource: 'host', objects: hostAffinity },
],
allPrivileges
)
assert.strictEqual(missingPrivileges.length, 0)
})
test('Should return only missing privileges', () => {
const missingPrivileges = getMissingPrivileges(
[
{ user, action: 'instantiate', resource: 'vm-template', objects: template },
{ user, action: 'create', resource: 'vdi', objects: vdis },
{ user, action: 'create', resource: 'vif', objects: vifs },
{ user, action: 'boot', resource: 'vdi', objects: iso },
{ user, action: 'allow-vm', resource: 'host', objects: hostAffinity },
{ user, action: 'read', resource: 'pool', objects: poolWithoutPrivilege },
{ user, action: '*', resource: 'pool', objects: poolWithoutPrivilege },
],
allPrivileges
)
assert.strictEqual(missingPrivileges.length, 2)
})
test('Should return objectId if only one object in the missing privilege', () => {
const missingPrivileges = getMissingPrivileges(
[{ user, action: 'read', resource: 'pool', objects: poolWithoutPrivilege }],
allPrivileges
)
assert.strictEqual(missingPrivileges.length, 1)
assert.notStrictEqual(missingPrivileges[0]!.objectId, undefined)
})
test('Should return objectIds if multiple objects in the missing privilege', () => {
const missingPrivileges = getMissingPrivileges(
[{ user, action: 'read', resource: 'pool', objects: [poolWithoutPrivilege, poolWithoutPrivilege] }],
allPrivileges
)
assert.strictEqual(missingPrivileges.length, 1)
assert.notStrictEqual(missingPrivileges[0]!.objectIds, undefined)
})
})
suite('hasPrivileges behavior', () => {
test('Should return false if at least one privilege deny', () => {
const result = hasPrivileges(
[
{ user, action: 'instantiate', resource: 'vm-template', objects: templateDeny },
{ user, action: 'create', resource: 'vdi', objects: vdis },
{ user, action: 'create', resource: 'vif', objects: vifs },
{ user, action: 'boot', resource: 'vdi', objects: iso },
{ user, action: 'allow-vm', resource: 'host', objects: hostAffinity },
],
allPrivileges
)
assert.strictEqual(result, false)
})
test("Should return false if at least one privilege didn't match", () => {
const result = hasPrivileges(
[
{ user, action: 'instantiate', resource: 'vm-template', objects: template },
{ user, action: 'create', resource: 'vdi', objects: vdisMultipleSrs },
{ user, action: 'create', resource: 'vif', objects: vifs },
{ user, action: 'boot', resource: 'vdi', objects: iso },
{ user, action: 'allow-vm', resource: 'host', objects: hostAffinity },
],
allPrivileges
)
assert.strictEqual(result, false)
})
})
suite('filterObjectsWithPrivilege behavior', () => {
test('Should return only objects that match privileges', () => {
let vms = [almaVm, alpineVm, xoaVm, xoaVm]
assert.strictEqual(vms.length, 4)
vms = filterObjectsWithPrivilege({
user,
action: 'read',
resource: 'vm',
objects: vms,
userPrivileges: allPrivileges,
})
assert.strictEqual(vms.length, 2)
})
})
})
suite('getActionStrings', () => {
test('returns flat actions for a resource without nesting', () => {
const actions = getActionStrings('host')
assert.ok(actions.includes('read'))
// @ts-ignore A TypeScript error is thrown because `foo` is not of type `SupportedActions<'vm'>`.
// To test for a negative value, the error is ignored.
assert.strictEqual(actions.includes('foo'), false)
})
test('parent action is included alongside its children', () => {
const actions = getActionStrings('vm')
assert.ok(actions.includes('shutdown'))
assert.ok(actions.includes('shutdown:clean'))
assert.ok(actions.includes('shutdown:hard'))
})
})

View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"module": "nodenext",
"lib": ["ESNext"],
"moduleResolution": "nodenext",
"target": "ES2022",
"skipLibCheck": true,
"types": ["node"], // Only use types from Node.js and packages that are explicitly imported
"noImplicitAny": false, // otherwise it will throw an error when importing packages without type definition
"noUncheckedIndexedAccess": true,
"declaration": true
},
"include": ["./src/**/*"],
"exclude": ["./node_modules", "./dist"]
}

View File

@@ -1,7 +1,7 @@
{
"private": false,
"name": "@xen-orchestra/async-map",
"version": "0.1.2",
"version": "0.1.3",
"license": "ISC",
"description": "Promise.all + map for all iterables",
"keywords": [
@@ -28,7 +28,7 @@
"node": ">=6"
},
"dependencies": {
"lodash": "^4.17.4"
"lodash": "^4.18.0"
},
"scripts": {
"postversion": "npm publish",

View File

@@ -17,7 +17,7 @@
},
"dependencies": {
"@vates/decorate-with": "^2.1.0",
"@xen-orchestra/log": "^0.7.1",
"@xen-orchestra/log": "^0.7.2",
"golike-defer": "^0.5.1",
"object-hash": "^2.0.1"
},

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