# GlusterFS storage UI for Proxmox VE (storage type, volume scan ComboBox, input panel).
# Tested against: pve-manager 9.2.3

--- a/usr/share/pve-manager/js/pvemanagerlib.js	2026-03-31 15:00:30.969714911 -0700
+++ b/usr/share/pve-manager/js/pvemanagerlib.js	2026-03-31 15:04:38.226558211 -0700
@@ -2327,6 +2327,12 @@
                 faIcon: 'building',
                 backups: true,
             },
+            glusterfs: {
+                name: 'GlusterFS',
+                ipanel: 'GlusterFsInputPanel',
+                faIcon: 'building',
+                backups: true,
+            },
             iscsi: {
                 name: 'iSCSI',
                 ipanel: 'IScsiInputPanel',
@@ -63959,7 +63965,7 @@
             }
         };
 
-        const qemuImgStorageTypes = ['dir', 'btrfs', 'nfs', 'cifs'];
+        const qemuImgStorageTypes = ['dir', 'btrfs', 'nfs', 'cifs', 'glusterfs'];
 
         if (qemuImgStorageTypes.includes(me.type)) {
             addAdvancedWidget({
@@ -67903,3 +67909,119 @@
         });
     },
 });
+Ext.define('PVE.storage.GlusterFsScan', {
+    extend: 'Ext.form.field.ComboBox',
+    alias: 'widget.pveGlusterFsScan',
+
+    queryParam: 'server',
+
+    valueField: 'volname',
+    displayField: 'volname',
+    matchFieldWidth: false,
+    listConfig: {
+        loadingText: 'Scanning...',
+        width: 350,
+    },
+    doRawQuery: function () {
+        // nothing
+    },
+
+    onTriggerClick: function () {
+        var me = this;
+
+        if (!me.queryCaching || me.lastQuery !== me.glusterServer) {
+            me.store.removeAll();
+        }
+
+        me.allQuery = me.glusterServer;
+
+        me.callParent();
+    },
+
+    setServer: function (server) {
+        var me = this;
+
+        me.glusterServer = server;
+    },
+
+    initComponent: function () {
+        var me = this;
+
+        if (!me.nodename) {
+            me.nodename = 'localhost';
+        }
+
+        var store = Ext.create('Ext.data.Store', {
+            fields: ['volname'],
+            proxy: {
+                type: 'proxmox',
+                url: '/api2/json/nodes/' + me.nodename + '/scan/glusterfs',
+            },
+        });
+
+        store.sort('volname', 'ASC');
+
+        Ext.apply(me, {
+            store: store,
+        });
+
+        me.callParent();
+    },
+});
+
+Ext.define('PVE.storage.GlusterFsInputPanel', {
+    extend: 'PVE.panel.StorageBase',
+
+    onlineHelp: 'storage_glusterfs',
+
+    initComponent: function () {
+        var me = this;
+
+        me.column1 = [
+            {
+                xtype: me.isCreate ? 'textfield' : 'displayfield',
+                name: 'server',
+                value: '',
+                fieldLabel: gettext('Server'),
+                allowBlank: false,
+                listeners: {
+                    change: function (f, value) {
+                        if (me.isCreate) {
+                            let volumeField = me.down('field[name=volume]');
+                            volumeField.setServer(value);
+                            volumeField.setValue('');
+                        }
+                    },
+                },
+            },
+            {
+                xtype: me.isCreate ? 'proxmoxtextfield' : 'displayfield',
+                name: 'backup-volfile-servers',
+                vtype: 'HostList',
+                value: '',
+                deleteEmpty: !me.isCreate,
+                fieldLabel: gettext('Backup Volfile Servers'),
+                emptyText: gettext('Comma-separated list'),
+                allowBlank: true,
+            },
+            {
+                xtype: me.isCreate ? 'pveGlusterFsScan' : 'displayfield',
+                name: 'volume',
+                value: '',
+                fieldLabel: 'Volume name',
+                allowBlank: false,
+            },
+            {
+                xtype: 'pveContentTypeSelector',
+                cts: ['images', 'iso', 'backup', 'vztmpl', 'snippets', 'import', 'rootdir'],
+                name: 'content',
+                value: 'images',
+                multiSelect: true,
+                fieldLabel: gettext('Content'),
+                allowBlank: false,
+            },
+        ];
+
+        me.callParent();
+    },
+});
