public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [PATCH manager v3 0/2] fix #6855: ui: storage: add mount options field for nfs, cifs, cephfs
@ 2026-07-30  9:40 Elias Huhsovitz
  2026-07-30  9:40 ` [PATCH manager v3 1/2] fix #6855: ui: storage: add mount options field for nfs amd smb/cifs Elias Huhsovitz
  2026-07-30  9:40 ` [PATCH manager v3 2/2] ui: storage: add mount options field for cephfs Elias Huhsovitz
  0 siblings, 2 replies; 3+ messages in thread
From: Elias Huhsovitz @ 2026-07-30  9:40 UTC (permalink / raw)
  To: pve-devel; +Cc: Elias Huhsovitz

Setting custom mount options for NFS, CIFS and CephFS storage
requires users to use the CLI or edit `/etc/pve/storage.cfg`.
This series adds a new `Mount Options` text field allowing users to 
add custom mount options directly via the web interface. 

The field is located in the `Advanced` section of the edit window.

Bugzilla: https://bugzilla.proxmox.com/show_bug.cgi?id=6855

v2: https://lore.proxmox.com/pve-devel/20260723150316.168440-1-e.huhsovitz@proxmox.com/
v1: https://lore.proxmox.com/pve-devel/20260721121334.115027-1-e.huhsovitz@proxmox.com/

This version extends the UI update to CephFS for the sake of
completion, as the backend API already exposes the options
parameter for it. Additionally, it fixes a logic flaw where the
remount warning hint was incorrectly displayed when adding a
new storage.

Patch 1/2 add mount options field for NFS and CIFS.

Patch 2/2 add mount options field for CephFS.
This was not mentioned in the original bug report, but users have
encountered this issue before:
https://forum.proxmox.com/threads/storage-cephfs-and-multiple-file-systems-passing-options.55018/

Changes v2 -> v3:
- Extend the mount options UI field to CephFS for consistency,
  as the backend API already supports this parameter.
- Fix the dynamic hint logic to only display when editing an
  existing storage and the configuration is actively changed.
  This prevents the hint from showing unnecessarily during
  initial creation.
- Fix a typo in the commit message and harmonize formatting.

Changes v1 -> v2:
- Add a dynamic UI hint when a user changes the mount config
  or NFS version during an edit, informing them that a remount
  is required for changes to take effect.
- Update emptyText placeholders to align with existing Proxmox
  VE UI conventions.
- Add filtering of exact duplicate options (for example,
  "rdma, seal, rdma" becomes "rdma, seal").  Deep semantic
  deduplication (for example, "mount_timeout=60, mount_timeout=70")
  is omitted to preserve opaque mount options where such
  configurations might be desired by the underlying system.

  Note: This was already implemented in the v2 patch, but was
  accidentally omitted from the v1 -> v2 changelog.

Comments that have not been implemented:
- Automatic re-mount:
  Automatically triggering a remount could introduce
  unnecessary complexity and risk disrupting active I/O
  operations if a user modifies options while the storage is
  currently in use by running guests or backup jobs. If users
  need this functionality, it can be added in a separate,
  dedicated patch.

Big Thanks to Max for reviewing the previous versions!

Elias Huhsovitz (2):
  fix #6855: ui: storage: add mount options field for nfs amd smb/cifs
  ui: storage: add mount options field for cephfs

 www/manager6/storage/CIFSEdit.js   |  69 ++++++++++++++++--
 www/manager6/storage/CephFSEdit.js |  59 ++++++++++++++-
 www/manager6/storage/NFSEdit.js    | 112 +++++++++++++++++++++++------
 3 files changed, 210 insertions(+), 30 deletions(-)

-- 
2.47.3





^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH manager v3 1/2] fix #6855: ui: storage: add mount options field for nfs amd smb/cifs
  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
  2026-07-30  9:40 ` [PATCH manager v3 2/2] ui: storage: add mount options field for cephfs Elias Huhsovitz
  1 sibling, 0 replies; 3+ messages in thread
From: Elias Huhsovitz @ 2026-07-30  9:40 UTC (permalink / raw)
  To: pve-devel; +Cc: Elias Huhsovitz

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





^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH manager v3 2/2] ui: storage: add mount options field for cephfs
  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 ` [PATCH manager v3 1/2] fix #6855: ui: storage: add mount options field for nfs amd smb/cifs Elias Huhsovitz
@ 2026-07-30  9:40 ` Elias Huhsovitz
  1 sibling, 0 replies; 3+ messages in thread
From: Elias Huhsovitz @ 2026-07-30  9:40 UTC (permalink / raw)
  To: pve-devel; +Cc: Elias Huhsovitz

Add a text field for custom mount options to the CephFS
storage configuration dialog. This allows users to configure
advanced mount parameters (like 'ms_mode' or 'mount_timeout')
directly via the web interface, without needing to use the CLI
or manually edit storage.cfg.

When mount options are being changed:
Display a hint to the user that changes will require
remounting the storage to take effect.

Reduce the usage of `var` keyword in CephFSEdit.js.

Signed-off-by: Elias Huhsovitz <e.huhsovitz@proxmox.com>
---
 www/manager6/storage/CephFSEdit.js | 59 +++++++++++++++++++++++++++++-
 1 file changed, 58 insertions(+), 1 deletion(-)

diff --git a/www/manager6/storage/CephFSEdit.js b/www/manager6/storage/CephFSEdit.js
index db54df87..1c7a9bd9 100644
--- a/www/manager6/storage/CephFSEdit.js
+++ b/www/manager6/storage/CephFSEdit.js
@@ -8,17 +8,46 @@ Ext.define('PVE.storage.CephFSInputPanel', {
         type: 'cephstorage',
     },
 
+    onGetValues: function (values) {
+        const me = this;
+
+        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) {
         if (values.monhost) {
             this.viewModel.set('pveceph', false);
             this.lookupReference('pvecephRef').setValue(false);
             this.lookupReference('pvecephRef').resetOriginalValue();
         }
+
+        this.originalOptions = values.options || '';
+
         this.callParent([values]);
+
+        const hint = this.down('#mountOptionsHint');
+        if (hint) {
+            hint.setHidden(true);
+        }
     },
 
     initComponent: function () {
-        var me = this;
+        const me = this;
 
         if (!me.nodename) {
             me.nodename = 'localhost';
@@ -132,6 +161,34 @@ Ext.define('PVE.storage.CephFSInputPanel', {
             },
         ];
 
+        me.advancedColumnB = me.advancedColumnB || [];
+
+        me.advancedColumnB.push({
+            xtype: 'textfield',
+            name: 'options',
+            fieldLabel: gettext('Mount Options'),
+            emptyText: `${gettext('Example')}: ms_mode=secure, mount_timeout=60`,
+            allowBlank: true,
+            listeners: {
+                change: function (field, newValue) {
+                    const hint = me.down('#mountOptionsHint');
+                    if (hint) {
+                        hint.setHidden(me.isCreate || (newValue || '') === me.originalOptions);
+                    }
+                },
+            },
+        });
+
+        me.advancedColumnB.push({
+            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





^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-07-30  9:41 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH manager v3 1/2] fix #6855: ui: storage: add mount options field for nfs amd smb/cifs Elias Huhsovitz
2026-07-30  9:40 ` [PATCH manager v3 2/2] ui: storage: add mount options field for cephfs Elias Huhsovitz

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