diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 9ed09428b6a..5792286c8e1 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -10,6 +10,7 @@ - [Host/Advanced] Allow to force _Smart reboot_ if some resident VMs have the suspend operation blocked [Forum#7136](https://xcp-ng.org/forum/topic/7136/suspending-vms-during-host-reboot/23) (PR [#7025](https://github.com/vatesfr/xen-orchestra/pull/7025)) - [Plugin/backup-report] Errors are now listed in XO tasks - [PIF] Show network name in PIF selectors (PR [#7081](https://github.com/vatesfr/xen-orchestra/pull/7081)) +- [VM/Advanced] Possibility to create/delete VTPM [#7066](https://github.com/vatesfr/xen-orchestra/issues/7066) [Forum#6578](https://xcp-ng.org/forum/topic/6578/xcp-ng-8-3-public-alpha/109) (PR [#7085](https://github.com/vatesfr/xen-orchestra/pull/7085)) ### Bug fixes diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index 609f8c9f8bb..2ec18f526d1 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -25,6 +25,7 @@ const messages = { esxiImportStopOnErrorDescription: 'Stop on the first error when importing VMs', nImportVmsInParallel: 'Number of VMs to import in parallel', stopOnError: 'Stop on error', + uuid: 'UUID', vmSrUsage: 'Storage: {used} used of {total} ({free} free)', notDefined: 'Not defined', @@ -1367,6 +1368,9 @@ const messages = { logAction: 'Action', // ----- VM advanced tab ----- + createVtpm: 'Create a VTPM', + deleteVtpm: 'Delete the VTPM', + deleteVtpmWarning: 'If the VTPM is in use, removing it will result in a dangerous data loss. Are you sure you want to remove the VTPM?', vmRemoveButton: 'Remove', vmConvertToTemplateButton: 'Convert to template', vmSwitchVirtualizationMode: 'Convert to {mode}', @@ -1396,9 +1400,12 @@ const messages = { srHaTooltip: 'SR used for High Availability', nestedVirt: 'Nested virtualization', vmAffinityHost: 'Affinity host', + vmNeedToBeHalted: 'The VM needs to be halted', vmVga: 'VGA', vmVideoram: 'Video RAM', vmNicType: 'NIC type', + vtpm: 'VTPM', + vtpmRequireUefi: 'A UEFI boot firmware is necessary to use a VTPM', noAffinityHost: 'None', originalTemplate: 'Original template', unknownOsName: 'Unknown', @@ -1644,8 +1651,10 @@ const messages = { newVmNetworkConfigDoc: 'Network config documentation', templateHasBiosStrings: 'The template already contains the BIOS strings', secureBootLinkToDocumentationMessage: 'Click for more information about Guest UEFI Secure Boot.', + seeVtpmDocumentation: 'See VTPM documentation', vmBootFirmwareIsUefi: 'The boot firmware is UEFI', destroyCloudConfigVdiAfterBoot: 'Destroy cloud config drive after first boot', + vtpmNotSupported: 'VTPM is only supported on pools running XCP-ng/XS 8.3 or later.', // ----- Self ----- resourceSets: 'Resource sets', diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js index 07ff07d71d7..9db129306fa 100644 --- a/packages/xo-web/src/common/xo/index.js +++ b/packages/xo-web/src/common/xo/index.js @@ -2149,6 +2149,11 @@ export const deleteAclRule = ({ protocol = undefined, port = undefined, ipRange vifId: resolveId(vif), }) +// VTPM ----------------------------------------------------------- + +export const createVtpm = vm => _call('vtpm.create', { id: resolveId(vm) }) +export const deleteVtpm = vtpm => _call('vtpm.destroy', { id: resolveId(vtpm) }) + // Network ----------------------------------------------------------- export const editNetwork = (network, props) => _call('network.set', { ...props, id: resolveId(network) }) diff --git a/packages/xo-web/src/xo-app/vm/tab-advanced.js b/packages/xo-web/src/xo-app/vm/tab-advanced.js index c4969faea90..77e77d54f65 100644 --- a/packages/xo-web/src/xo-app/vm/tab-advanced.js +++ b/packages/xo-web/src/xo-app/vm/tab-advanced.js @@ -2,6 +2,7 @@ import _ from 'intl' import ActionButton from 'action-button' import Component from 'base-component' import decorate from 'apply-decorators' +import Copiable from 'copiable' import defined, { get } from '@xen-orchestra/defined' import getEventValue from 'get-event-value' import Icon from 'icon' @@ -28,8 +29,10 @@ import { cloneVm, convertVmToTemplate, createVgpu, + createVtpm, deleteVgpu, deleteVm, + deleteVtpm, editVm, getVmsHaValues, isVmRunning, @@ -450,9 +453,48 @@ export default class TabAdvanced extends Component { _onNicTypeChange = value => editVm(this.props.vm, { nicType: value === '' ? null : value }) + _getDisabledAddVtpmReason = createSelector( + () => this.props.vm, + () => this.props.pool, + (vm, pool) => { + if (!pool.vtpmSupported) { + return _('vtpmNotSupported') + } + if (vm.boot.firmware !== 'uefi') { + return _('vtpmRequireUefi') + } + if (vm.power_state !== 'Halted') { + return _('vmNeedToBeHalted') + } + } + ) + + _getDisabledDeleteVtpmReason = () => { + if (this.props.vm.power_state !== 'Halted') { + return _('vmNeedToBeHalted') + } + } + + _handleDeleteVtpm = async vtpm => { + await confirm({ + icon: 'delete', + title: _('deleteVtpm'), + body:

{_('deleteVtpmWarning')}

, + strongConfirm: { + messageId: 'deleteVtpm', + }, + }) + return deleteVtpm(vtpm) + } + render() { const { container, isAdmin, vgpus, vm, vmPool } = this.props const isWarmMigrationAvailable = getXoaPlan().value >= PREMIUM.value + const addVtpmTooltip = this._getDisabledAddVtpmReason() + const deleteVtpmTooltip = this._getDisabledDeleteVtpmReason() + const isAddVtpmAvailable = addVtpmTooltip === undefined + const isDeleteVtpmAvailable = deleteVtpmTooltip === undefined + const vtpmId = vm.VTPMs[0] return ( @@ -798,6 +840,59 @@ export default class TabAdvanced extends Component { )} + + {_('vtpm')} + + {/* + FIXME: add documentation link + + {_('seeVtpmDocumentation')} + */} + {vtpmId === undefined ? ( + + + {_('createVtpm')} + + + ) : ( +
+ + + {_('deleteVtpm')} + + + + + + + + {vtpmId.slice(0, 4)} + + + +
{_('uuid')}
+
+ )} + + {vm.boot.firmware === 'uefi' && ( {_('secureBoot')}