# 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(); + }, +});