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 898831FF138 for ; Tue, 21 Jul 2026 14:14:11 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 9882821459; Tue, 21 Jul 2026 14:14:10 +0200 (CEST) From: Elias Huhsovitz To: pve-devel@lists.proxmox.com Subject: [PATCH manager] fix #6855: ui: storage: add mount options field for nfs amd smb/cifs Date: Tue, 21 Jul 2026 14:13:33 +0200 Message-ID: <20260721121334.115027-1-e.huhsovitz@proxmox.com> X-Mailer: git-send-email 2.47.3 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1784635990351 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.197 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_LOW -0.7 Sender listed at https://www.dnswl.org/, low trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: WL4VXPSHP7IEER2CBFJLLIH7ZJPG77A5 X-Message-ID-Hash: WL4VXPSHP7IEER2CBFJLLIH7ZJPG77A5 X-MailFrom: e.huhsovitz@proxmox.com 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 CC: Elias Huhsovitz X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add a text field for custom mount options to the NFS and CIFS storage configuration dialogs. This allows users to configure advanced mount parameters (like 'nconnect', 'rdma', or 'seal') directly via the web interface, without needing to use the CLI or manually edit storage.cfg. Reduce the usage of legacy JS practices, such as 'var' & "+ string concatination" in affected functions. Signed-off-by: Elias Huhsovitz --- www/manager6/storage/CIFSEdit.js | 38 ++++++++++++++++--- www/manager6/storage/NFSEdit.js | 65 ++++++++++++++++++++++---------- 2 files changed, 77 insertions(+), 26 deletions(-) diff --git a/www/manager6/storage/CIFSEdit.js b/www/manager6/storage/CIFSEdit.js index 5fe3fefe..d34cd029 100644 --- a/www/manager6/storage/CIFSEdit.js +++ b/www/manager6/storage/CIFSEdit.js @@ -121,7 +121,7 @@ Ext.define('PVE.storage.CIFSInputPanel', { onlineHelp: 'storage_cifs', onGetValues: function (values) { - let me = this; + const me = this; if (values.password?.length === 0) { delete values.password; @@ -133,11 +133,26 @@ Ext.define('PVE.storage.CIFSInputPanel', { delete values.subdir; } + if (values.options) { + values.options = values.options + .split(',') + .map((opt) => opt.trim()) + .filter((opt) => opt !== '') + .join(','); + } + + if (!values.options) { + delete values.options; + if (!me.isCreate) { + values.delete = values.delete ? `${values.delete},options` : 'options'; + } + } + return me.callParent([values]); }, initComponent: function () { - var me = this; + const me = this; me.column1 = [ { @@ -149,7 +164,7 @@ Ext.define('PVE.storage.CIFSInputPanel', { listeners: { change: function (f, value) { if (me.isCreate) { - let exportField = me.down('field[name=share]'); + const exportField = me.down('field[name=share]'); exportField.setServer(value); } }, @@ -166,7 +181,7 @@ Ext.define('PVE.storage.CIFSInputPanel', { if (!me.isCreate) { return; } - var exportField = me.down('field[name=share]'); + const exportField = me.down('field[name=share]'); exportField.setUsername(value); }, }, @@ -181,7 +196,7 @@ Ext.define('PVE.storage.CIFSInputPanel', { minLength: 1, listeners: { change: function (f, value) { - let exportField = me.down('field[name=share]'); + const exportField = me.down('field[name=share]'); exportField.setPassword(value); }, }, @@ -213,7 +228,7 @@ Ext.define('PVE.storage.CIFSInputPanel', { listeners: { change: function (f, value) { if (me.isCreate) { - let exportField = me.down('field[name=share]'); + const exportField = me.down('field[name=share]'); exportField.setDomain(value); } }, @@ -229,6 +244,17 @@ Ext.define('PVE.storage.CIFSInputPanel', { }, ]; + me.advancedColumn2 = [ + { + xtype: 'textfield', + name: 'options', + fieldLabel: gettext('Mount Options'), + emptyText: 'e.g., rdma, seal', + allowBlank: true, + deleteEmpty: !me.isCreate, + }, + ]; + me.callParent(); }, }); diff --git a/www/manager6/storage/NFSEdit.js b/www/manager6/storage/NFSEdit.js index 72db156f..c21759d8 100644 --- a/www/manager6/storage/NFSEdit.js +++ b/www/manager6/storage/NFSEdit.js @@ -57,7 +57,6 @@ Ext.define('PVE.storage.NFSScan', { me.callParent(); }, }); - Ext.define('PVE.storage.NFSInputPanel', { extend: 'PVE.panel.StorageBase', @@ -66,25 +65,31 @@ Ext.define('PVE.storage.NFSInputPanel', { options: [], onGetValues: function (values) { - var me = this; - - var i; - var res = []; - for (i = 0; i < me.options.length; i++) { - let item = me.options[i]; - if (!item.match(/^vers=(.*)$/)) { - res.push(item); - } + const me = this; + const res = []; + + if (values.options && values.options.trim() !== '') { + const opts = values.options.split(','); + opts.forEach((opt) => { + const cleanOpt = opt.trim(); + if (cleanOpt !== '' && !cleanOpt.match(/^vers=(.*)$/)) { + res.push(cleanOpt); + } + }); } + if (values.nfsversion && values.nfsversion !== '__default__') { - res.push('vers=' + values.nfsversion); + res.push(`vers=${values.nfsversion}`); } + delete values.nfsversion; - values.options = res.join(','); - if (values.options === '') { + + if (res.length > 0) { + values.options = res.join(','); + } else { delete values.options; if (!me.isCreate) { - values.delete = 'options'; + values.delete = values.delete ? `${values.delete},options` : 'options'; } } @@ -92,21 +97,33 @@ Ext.define('PVE.storage.NFSInputPanel', { }, setValues: function (values) { - var me = this; + const me = this; if (values.options) { - me.options = values.options.split(','); - me.options.forEach(function (item) { - var match = item.match(/^vers=(.*)$/); + const opts = values.options + .split(',') + .map((item) => item.trim()); + + // Extract "vers=..." for the combobox + opts.forEach((item) => { + const match = item.match(/^vers=(.*)$/); if (match) { values.nfsversion = match[1]; } }); + + // Filter out "vers=..." and empty strings so it doesn't appear duplicated + const otherOptions = opts.filter((item) => item !== '' && !item.match(/^vers=(.*)$/)); + values.options = otherOptions.join(','); + + if (values.options === '') { + delete values.options; + } } return me.callParent([values]); }, initComponent: function () { - var me = this; + const me = this; me.column1 = [ { @@ -118,7 +135,7 @@ Ext.define('PVE.storage.NFSInputPanel', { listeners: { change: function (f, value) { if (me.isCreate) { - let exportField = me.down('field[name=export]'); + const exportField = me.down('field[name=export]'); exportField.setServer(value); exportField.setValue(''); } @@ -143,6 +160,14 @@ Ext.define('PVE.storage.NFSInputPanel', { ]; me.advancedColumn2 = [ + { + xtype: 'textfield', + name: 'options', + fieldLabel: gettext('Mount Options'), + emptyText: 'e.g., nconnect=4, soft', + allowBlank: true, + deleteEmpty: !me.isCreate, + }, { xtype: 'proxmoxKVComboBox', fieldLabel: gettext('NFS Version'), -- 2.47.3