From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 6DA811FF09B for ; Mon, 26 Oct 2026 23:04:43 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 2E41B20C35; Mon, 26 Oct 2026 23:04:42 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=datenzelt.de; s=MBO0001; t=1793052277; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=8fDHo2XbbhQnpCU9KIytkP3PiCsvh9FGTT6cLsoaD7Q=; b=Eqo5eZlT8Bp0epHmPOHzYRUutDapbLVMtPp5oAdvpEjYFTZUkZy11z7GskbtD9lqP1JxcI VS0vux3fPy8zY9MSvWmG31YeoWuz5ELirj2UyFvG82AqSl4P9mGGr+/yrXCoHQMGR6kLJG dUepzXwWV8fJuFAYSy1Jh19CcdIC4Q+sA5kWgVbVzHXGjLHV8yYHM0jycJTk9p6ZPuk3E5 Ck0jb1gx+zhMEoqabFlUb7f4HtObBNhJw6sQezRipQwWS+lEYN84EYcMpwFkH23944yGQW k/rJsZxaJV4JTIqTa0NjWl3ppNq88ys8/3F4M6kqQf1g3IL/u2Px4JjzV7S6JQ== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of timo.meschkat@datenzelt.de designates 2001:67c:2050:b231:465::1 as permitted sender) smtp.mailfrom=timo.meschkat@datenzelt.de From: Timo Meschkat To: pve-devel@lists.proxmox.com Subject: [RFC PATCH manager 1/4] fix #3760: ui: qemu: allow selecting the memory unit Date: Mon, 26 Oct 2026 23:04:20 +0100 Message-ID: <20261026220423.127776-5-timo.meschkat@datenzelt.de> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261026220423.127776-1-timo.meschkat@datenzelt.de> References: <20261026220423.127776-1-timo.meschkat@datenzelt.de> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Rspamd-Queue-Id: 4jD72Y2Lmvz8sX3 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.350 Adjusted score from AWL reputation of From: address DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain DMARC_PASS -0.1 DMARC pass policy RCVD_IN_DNSWL_LOW -0.7 Sender listed at https://www.dnswl.org/, low trust SPF_HELO_PASS -0.001 SPF: HELO matches SPF record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: XOOT3U6JCC62MQMETIVTBXALVO6QDXNG X-Message-ID-Hash: XOOT3U6JCC62MQMETIVTBXALVO6QDXNG X-MailFrom: timo.meschkat@datenzelt.de X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Memory could only be entered in MiB so far, so larger values had to be converted by hand. Allow entering it in MiB, GiB or TiB, or typing it with a suffix like '8G'. MiB stays the default and is still what gets sent to the API. The minimum memory now follows both value and unit of the memory, as long as the user did not change it. The restore and guest import windows use the same field for their memory override. Signed-off-by: Timo Meschkat --- www/manager6/form/MemoryField.js | 59 ++++++++++++++---------------- www/manager6/qemu/MemoryEdit.js | 29 ++++++++++----- www/manager6/window/GuestImport.js | 5 ++- www/manager6/window/Restore.js | 4 ++ 4 files changed, 55 insertions(+), 42 deletions(-) diff --git a/www/manager6/form/MemoryField.js b/www/manager6/form/MemoryField.js index 1ca533b7..75c1a23a 100644 --- a/www/manager6/form/MemoryField.js +++ b/www/manager6/form/MemoryField.js @@ -1,7 +1,15 @@ Ext.define('PVE.form.MemoryField', { - extend: 'Ext.form.field.Number', + extend: 'Proxmox.form.SizeUnitField', alias: 'widget.pveMemoryField', + backendUnit: 'MiB', + + units: ['MiB', 'GiB', 'TiB'], + + unitSteps: { + MiB: 32, + }, + allowBlank: false, hotplug: false, @@ -10,21 +18,9 @@ Ext.define('PVE.form.MemoryField', { maxValue: 4178944, - step: 32, - value: '512', // qm backend default - allowDecimals: false, - - allowExponential: false, - computeUpDown: function (value) { - var me = this; - - if (!me.hotplug) { - return { up: value + me.step, down: value - me.step }; - } - var dimm_size = 512; var prev_dimm_size = 0; var min_size = 1024; @@ -50,20 +46,15 @@ Ext.define('PVE.form.MemoryField', { return { up: value_up, down: value_down, start: value_start }; }, - onSpinUp: function () { - var me = this; - if (!me.readOnly) { - let res = me.computeUpDown(me.getValue()); - me.setValue(Ext.Number.constrain(res.up, me.minValue, me.maxValue)); - } - }, + getSpinValue: function (value, up) { + let me = this; - onSpinDown: function () { - var me = this; - if (!me.readOnly) { - let res = me.computeUpDown(me.getValue()); - me.setValue(Ext.Number.constrain(res.down, me.minValue, me.maxValue)); + if (!me.hotplug) { + return me.callParent([value, up]); } + + let res = me.computeUpDown(value); + return up ? res.up : res.down; }, initComponent: function () { @@ -71,17 +62,23 @@ Ext.define('PVE.form.MemoryField', { if (me.hotplug) { me.minValue = 1024; + } - me.on('blur', function (field) { - var value = me.getValue(); - var res = me.computeUpDown(value); + me.callParent(); + + if (me.hotplug) { + // memory hotplug only works in DIMM sized steps + me.inputField.on('blur', function () { + let value = me.getValue(); + if (value === null) { + return; + } + let res = me.computeUpDown(value); if (value === res.start || value === res.up || value === res.down) { return; } - field.setValue(res.up); + me.setValue(res.up); }); } - - me.callParent(); }, }); diff --git a/www/manager6/qemu/MemoryEdit.js b/www/manager6/qemu/MemoryEdit.js index 0f6bd656..d8c54e08 100644 --- a/www/manager6/qemu/MemoryEdit.js +++ b/www/manager6/qemu/MemoryEdit.js @@ -79,22 +79,29 @@ Ext.define('PVE.qemu.MemoryInputPanel', { { xtype: 'pveMemoryField', labelWidth: labelWidth, - fieldLabel: gettext('Memory') + ' (MiB)', + fieldLabel: gettext('Memory'), + fieldDescription: gettext('Amount of memory (RAM) of the virtual machine'), name: 'memory', value: '512', // better defaults get set via the view controllers afterrender minValue: 1, - step: 32, hotplug: me.hotplug, listeners: { change: function (f, value, old) { - var bf = me.down('field[name=balloon]'); - var balloon = bf.getValue(); + let bf = me.down('pveMemoryField[name=balloon]'); bf.setMaxValue(value); - if (balloon === old) { + // follow memory until the user sets the minimum + if (bf.getValue() === old && !bf.hasUserInput()) { bf.setValue(value); + bf.setUnit(f.getUnit()); } bf.validate(); }, + unitchange: function (f, unit) { + let bf = me.down('pveMemoryField[name=balloon]'); + if (bf.getValue() === f.getValue() && !bf.hasUserInput()) { + bf.setUnit(unit); + } + }, }, }, ]; @@ -106,14 +113,16 @@ Ext.define('PVE.qemu.MemoryInputPanel', { minValue: 1, maxValue: me.insideWizard ? 2048 : 512, value: '512', // better defaults get set (indirectly) via the view controllers afterrender - step: 32, - fieldLabel: gettext('Minimum memory') + ' (MiB)', + fieldLabel: gettext('Minimum memory'), + fieldDescription: gettext( + 'Minimum amount of memory (RAM) the virtual machine keeps with ballooning', + ), hotplug: me.hotplug, labelWidth: labelWidth, allowBlank: false, listeners: { change: function (f, value) { - var memory = me.down('field[name=memory]').getValue(); + var memory = me.down('pveMemoryField[name=memory]').getValue(); var shares = me.down('field[name=shares]'); shares.setDisabled(value === memory); }, @@ -141,9 +150,9 @@ Ext.define('PVE.qemu.MemoryInputPanel', { fieldLabel: gettext('Ballooning Device'), listeners: { change: function (f, value) { - var bf = me.down('field[name=balloon]'); + var bf = me.down('pveMemoryField[name=balloon]'); var shares = me.down('field[name=shares]'); - var memory = me.down('field[name=memory]'); + var memory = me.down('pveMemoryField[name=memory]'); bf.setDisabled(!value); shares.setDisabled(!value || bf.getValue() === memory.getValue()); }, diff --git a/www/manager6/window/GuestImport.js b/www/manager6/window/GuestImport.js index 1013fe14..0b43ff76 100644 --- a/www/manager6/window/GuestImport.js +++ b/www/manager6/window/GuestImport.js @@ -515,7 +515,10 @@ Ext.define('PVE.window.GuestImport', { }, { xtype: 'pveMemoryField', - fieldLabel: gettext('Memory') + ' (MiB)', + fieldLabel: gettext('Memory'), + fieldDescription: gettext( + 'Amount of memory (RAM) of the virtual machine', + ), name: 'memory', reference: 'memoryField', value: 512, diff --git a/www/manager6/window/Restore.js b/www/manager6/window/Restore.js index e8d8de4e..f704fc40 100644 --- a/www/manager6/window/Restore.js +++ b/www/manager6/window/Restore.js @@ -359,6 +359,10 @@ Ext.define('PVE.window.Restore', { { xtype: 'pveMemoryField', fieldLabel: gettext('Memory'), + fieldDescription: + me.vmtype === 'lxc' + ? gettext('Amount of memory (RAM) of the container') + : gettext('Amount of memory (RAM) of the virtual machine'), name: 'memory', reference: 'memoryField', value: '', -- 2.47.3