diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 48c67f11a9..54dccd44bc 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -12,6 +12,8 @@ > Users must be able to say: "Nice enhancement, I'm eager to test it" - [XO6/Host] Add possibility to detach an host (PR [#10179](https://github.com/vatesfr/xen-orchestra/pull/10179)) +- [XO5/New VM] Ability to add the VM to a resource set and to share it during creation (PR [#10259](https://github.com/vatesfr/xen-orchestra/pull/10259)) + ### Bug fixes @@ -34,5 +36,7 @@ - @xen-orchestra/web minor +- xo-server minor +- xo-web minor diff --git a/packages/xo-server/src/api/vm.mjs b/packages/xo-server/src/api/vm.mjs index 2dfd073552..fbffdd498e 100644 --- a/packages/xo-server/src/api/vm.mjs +++ b/packages/xo-server/src/api/vm.mjs @@ -167,7 +167,9 @@ export const create = defer(async function ($defer, params) { let checkLimits if (resourceSet) { - await this.checkResourceSetConstraints(resourceSet, user.id, objectIds) + // an admin can add the created VM to a resource set even if the objects it + // uses (template, SRs, networks) are not part of this resource set + await this.checkResourceSetConstraints(resourceSet, user.id, user.permission === 'admin' ? undefined : objectIds) checkLimits = async limits2 => { const _limits = assignWith({}, limits, limits2, (l1 = 0, l2) => l1 + l2) await this.allocateLimitsInResourceSet(_limits, resourceSet) diff --git a/packages/xo-web/src/xo-app/new-vm/index.js b/packages/xo-web/src/xo-app/new-vm/index.js index 3ceb92c23b..b54dc5ec2a 100644 --- a/packages/xo-web/src/xo-app/new-vm/index.js +++ b/packages/xo-web/src/xo-app/new-vm/index.js @@ -304,9 +304,12 @@ export default class NewVm extends BaseComponent { this._initTemplate(template) } + // as an admin, the resource set is selected in the advanced settings and + // `share` is initialized by `_setResourceSet`, it must not be overridden here if ( - !isEqual(prevProps.resourceSets, this.props.resourceSets) || - prevProps.location.query.resourceSet !== this.props.location.query.resourceSet + this.state.state.resourceSet === undefined && + (!isEqual(prevProps.resourceSets, this.props.resourceSets) || + prevProps.location.query.resourceSet !== this.props.location.query.resourceSet) ) { this._setState({ share: this._getResourceSet()?.shareByDefault ?? false, @@ -385,6 +388,7 @@ export default class NewVm extends BaseComponent { nameLabels: map(Array(NB_VMS_MIN), (_, index) => `VM_${index + 1}`), namePattern: '{name}%', nbVms: NB_VMS_MIN, + resourceSet: undefined, VDIs: [], VIFs: [], secureBoot: false, @@ -501,7 +505,9 @@ export default class NewVm extends BaseComponent { return _vif }) - const resourceSet = this._getResourceSet() + // - self user: resource set ID in the URL, + // - admin: resource set in state + const resourceSet = this._getResourceSet() ?? state.resourceSet const { template } = this.props // Either use `memory` OR `memory*` params @@ -844,6 +850,14 @@ export default class NewVm extends BaseComponent { }) this._reset() } + // admin only + _setResourceSet = resourceSet => { + this._setState({ + // the select gives `null` when cleared, `undefined` is expected by `vm.create` + resourceSet: resourceSet ?? undefined, + share: resourceSet?.shareByDefault ?? false, + }) + } _selectPool = pool => { const { pathname } = this.props.location @@ -1634,6 +1648,7 @@ export default class NewVm extends BaseComponent { nameLabels, namePattern, nbVms, + resourceSet, secureBoot, seqStart, share, @@ -1948,6 +1963,23 @@ export default class NewVm extends BaseComponent { ), + isAdmin && this._getResourceSet() === undefined && ( + + + + + + + ), ]} )