public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Elias Huhsovitz <e.huhsovitz@proxmox.com>
To: pve-devel@lists.proxmox.com
Cc: Elias Huhsovitz <e.huhsovitz@proxmox.com>
Subject: [PATCH manager v3 1/2] fix #6855: ui: storage: add mount options field for nfs amd smb/cifs
Date: Thu, 30 Jul 2026 11:40:54 +0200	[thread overview]
Message-ID: <20260730094055.57659-2-e.huhsovitz@proxmox.com> (raw)
In-Reply-To: <20260730094055.57659-1-e.huhsovitz@proxmox.com>

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.

When nfs version or mount option is being changed:
Display a hint to the user that changes will require
remounting the storage to take effect.

Reduce the usage of legacy JS practices, such as `var` & `+` string
concatination in affected functions.

Remove unused class property `options`.

Signed-off-by: Elias Huhsovitz <e.huhsovitz@proxmox.com>
---
 www/manager6/storage/CIFSEdit.js |  69 +++++++++++++++++--
 www/manager6/storage/NFSEdit.js  | 112 ++++++++++++++++++++++++-------
 2 files changed, 152 insertions(+), 29 deletions(-)

diff --git a/www/manager6/storage/CIFSEdit.js b/www/manager6/storage/CIFSEdit.js
index 5fe3fefe..47e2118a 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,39 @@ Ext.define('PVE.storage.CIFSInputPanel', {
             delete values.subdir;
         }
 
+        if (values.options) {
+            values.options = values.options
+                .split(',')
+                .map((opt) => opt.trim())
+                .filter((opt, index, self) => opt !== '' && self.indexOf(opt) === index)
+                .join(',');
+        }
+
+        if (!values.options) {
+            delete values.options;
+            if (!me.isCreate) {
+                values.delete = values.delete ? `${values.delete},options` : 'options';
+            }
+        }
+
         return me.callParent([values]);
     },
 
+    setValues: function (values) {
+        const me = this;
+
+        me.originalOptions = values.options || '';
+
+        me.callParent([values]);
+
+        const hint = me.down('#mountOptionsHint');
+        if (hint) {
+            hint.setHidden(true);
+        }
+    },
+
     initComponent: function () {
-        var me = this;
+        const me = this;
 
         me.column1 = [
             {
@@ -149,7 +177,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 +194,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 +209,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 +241,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 +257,35 @@ Ext.define('PVE.storage.CIFSInputPanel', {
             },
         ];
 
+        me.advancedColumn2 = [
+            {
+                xtype: 'textfield',
+                name: 'options',
+                fieldLabel: gettext('Mount Options'),
+                emptyText: `${gettext('Example')}: rdma, seal`,
+                allowBlank: true,
+                listeners: {
+                    change: function (field, newValue) {
+                        const hint = me.down('#mountOptionsHint');
+                        if (hint) {
+                            hint.setHidden(me.isCreate || (newValue || '') === me.originalOptions);
+                        }
+                    },
+                },
+            },
+        ];
+
+        me.advancedColumnB = me.advancedColumnB || [];
+        me.advancedColumnB.unshift({
+            xtype: 'displayfield',
+            itemId: 'mountOptionsHint',
+            userCls: 'pmx-hint',
+            hidden: true,
+            value: gettext(
+                'Changes to mount options require remounting the storage to take effect.',
+            ),
+        });
+
         me.callParent();
     },
 });
diff --git a/www/manager6/storage/NFSEdit.js b/www/manager6/storage/NFSEdit.js
index 72db156f..68f4a8ce 100644
--- a/www/manager6/storage/NFSEdit.js
+++ b/www/manager6/storage/NFSEdit.js
@@ -57,34 +57,37 @@ Ext.define('PVE.storage.NFSScan', {
         me.callParent();
     },
 });
-
 Ext.define('PVE.storage.NFSInputPanel', {
     extend: 'PVE.panel.StorageBase',
 
     onlineHelp: 'storage_nfs',
 
-    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.includes(cleanOpt)) {
+                    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 +95,42 @@ 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());
+
+            opts.forEach((item) => {
+                const match = item.match(/^vers=(.*)$/);
                 if (match) {
                     values.nfsversion = match[1];
                 }
             });
+
+            // Filter out "vers=..." and empty strings
+            const otherOptions = opts.filter(
+                (item, index, self) => item !== '' && !item.match(/^vers=(.*)$/),
+            );
+            values.options = otherOptions.join(',');
+
+            if (values.options === '') {
+                delete values.options;
+            }
+        }
+
+        me.originalOptions = values.options || '';
+        me.originalNfsVersion = values.nfsversion || '__default__';
+
+        me.callParent([values]);
+
+        const hint = me.down('#mountOptionsHint');
+        if (hint) {
+            hint.setHidden(true);
         }
-        return me.callParent([values]);
     },
 
     initComponent: function () {
-        var me = this;
+        const me = this;
 
         me.column1 = [
             {
@@ -118,7 +142,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 +167,25 @@ Ext.define('PVE.storage.NFSInputPanel', {
         ];
 
         me.advancedColumn2 = [
+            {
+                xtype: 'textfield',
+                name: 'options',
+                fieldLabel: gettext('Mount Options'),
+                emptyText: `${gettext('Example')}: nconnect=4, soft`,
+                allowBlank: true,
+                listeners: {
+                    change: function (field, newValue) {
+                        const hint = me.down('#mountOptionsHint');
+                        if (hint) {
+                            const currentVersion =
+                                me.down('field[name=nfsversion]')?.getValue() || '__default__';
+                            const isOptionsChanged = (newValue || '') !== me.originalOptions;
+                            const isVersionChanged = currentVersion !== me.originalNfsVersion;
+                            hint.setHidden(me.isCreate || !(isOptionsChanged || isVersionChanged));
+                        }
+                    },
+                },
+            },
             {
                 xtype: 'proxmoxKVComboBox',
                 fieldLabel: gettext('NFS Version'),
@@ -156,9 +199,32 @@ Ext.define('PVE.storage.NFSInputPanel', {
                     ['4.1', '4.1'],
                     ['4.2', '4.2'],
                 ],
+                listeners: {
+                    change: function (field, newValue) {
+                        const hint = me.down('#mountOptionsHint');
+                        if (hint) {
+                            const currentOptions = me.down('field[name=options]')?.getValue() || '';
+                            const isOptionsChanged = currentOptions !== me.originalOptions;
+                            const isVersionChanged =
+                                (newValue || '__default__') !== me.originalNfsVersion;
+                            hint.setHidden(me.isCreate || !(isOptionsChanged || isVersionChanged));
+                        }
+                    },
+                },
             },
         ];
 
+        me.advancedColumnB = me.advancedColumnB || [];
+        me.advancedColumnB.unshift({
+            xtype: 'displayfield',
+            itemId: 'mountOptionsHint',
+            userCls: 'pmx-hint',
+            hidden: true,
+            value: gettext(
+                'Changes to mount options require remounting the storage to take effect.',
+            ),
+        });
+
         me.callParent();
     },
 });
-- 
2.47.3





  reply	other threads:[~2026-07-30  9:41 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-30  9:40 [PATCH manager v3 0/2] fix #6855: ui: storage: add mount options field for nfs, cifs, cephfs Elias Huhsovitz
2026-07-30  9:40 ` Elias Huhsovitz [this message]
2026-07-30  9:40 ` [PATCH manager v3 2/2] ui: storage: add mount options field for cephfs Elias Huhsovitz

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260730094055.57659-2-e.huhsovitz@proxmox.com \
    --to=e.huhsovitz@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal