From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id E74DB1FF0ED for ; Fri, 31 Jul 2026 12:31:09 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id D11E4215AA; Fri, 31 Jul 2026 12:30:53 +0200 (CEST) From: Dietmar Maurer To: pve-devel@lists.proxmox.com Subject: [RFC pve-manager 23/27] ui: node: add SAN LUNs panel Date: Fri, 31 Jul 2026 12:21:52 +0200 Message-ID: <20260731102156.3947857-24-dietmar@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260731102156.3947857-1-dietmar@proxmox.com> References: <20260731102156.3947857-1-dietmar@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 2 AWL -0.130 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) KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 1.274 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Message-ID-Hash: RZPD2ZHRMSF4EK3RCNITENIL63X54QAE X-Message-ID-Hash: RZPD2ZHRMSF4EK3RCNITENIL63X54QAE X-MailFrom: dietmar@zilli.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 X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: node > Disks intentionally lists only local disks, so devices attached through a SAN or a software-managed initiator connection (FC, SAS, iSCSI, NVMe-oF) are otherwise only visible inside the FC storage wizard. Add a read-only panel below the disk related views that lists them per node, including the multipath path count and which storage uses each LUN, for setup verification and troubleshooting. The entry is only shown with Datastore.Allocate, which the san-luns scan endpoint requires. Signed-off-by: Dietmar Maurer --- www/manager6/Makefile | 1 + www/manager6/node/Config.js | 14 ++++ www/manager6/node/SANLuns.js | 154 +++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 www/manager6/node/SANLuns.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 532d4d55..c354b3aa 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -245,6 +245,7 @@ JSSRC= \ node/Directory.js \ node/LVM.js \ node/LVMThin.js \ + node/SANLuns.js \ node/StatusView.js \ node/Subscription.js \ node/Summary.js \ diff --git a/www/manager6/node/Config.js b/www/manager6/node/Config.js index 217ee284..6329241a 100644 --- a/www/manager6/node/Config.js +++ b/www/manager6/node/Config.js @@ -357,6 +357,20 @@ Ext.define('PVE.node.Config', { groups: ['storage'], xtype: 'pveZFSList', }, + ); + + // the san-luns scan endpoint requires Datastore.Allocate + if (caps.storage['Datastore.Allocate']) { + me.items.push({ + xtype: 'pveSANLunList', + title: gettext('SAN LUNs'), + itemId: 'san-luns', + iconCls: 'fa fa-cubes', + groups: ['storage'], + }); + } + + me.items.push( { xtype: 'pveNodeCephStatus', title: 'Ceph', diff --git a/www/manager6/node/SANLuns.js b/www/manager6/node/SANLuns.js new file mode 100644 index 00000000..b2b9e24d --- /dev/null +++ b/www/manager6/node/SANLuns.js @@ -0,0 +1,154 @@ +Ext.define('PVE.node.SANLunList', { + extend: 'Ext.grid.GridPanel', + xtype: 'pveSANLunList', + + stateful: true, + stateId: 'grid-node-san-luns', + + disableSelection: true, + + emptyText: gettext( + 'No SAN attached devices found - "Show all devices" also lists devices with local or unknown transports', + ), + + viewConfig: { + trackOver: false, + }, + + columns: [ + { + header: gettext('Device'), + dataIndex: 'devpath', + flex: 2, + renderer: PVE.Utils.render_san_lun_device, + }, + { + header: gettext('Transport'), + dataIndex: 'transport', + width: 100, + }, + { + header: gettext('Size'), + dataIndex: 'size', + width: 100, + renderer: Proxmox.Utils.format_size, + }, + { + header: gettext('Vendor') + '/' + gettext('Model'), + dataIndex: 'model', + flex: 1, + renderer: (value, metaData, rec) => + Ext.htmlEncode(`${rec.get('vendor') || ''} ${value || ''}`.trim()), + }, + { + header: 'WWN', + dataIndex: 'wwn', + width: 200, + }, + { + header: gettext('Serial'), + dataIndex: 'serial', + width: 140, + hidden: true, + }, + { + header: gettext('Usage'), + dataIndex: 'usage', + flex: 2, + renderer: PVE.Utils.render_san_lun_usage, + }, + ], + + reload: function () { + this.getStore().load(); + }, + + listeners: { + activate: function () { + this.reload(); + }, + }, + + initComponent: function () { + let me = this; + + me.nodename = me.pveSelNode.data.node; + if (!me.nodename) { + throw 'no node name specified'; + } + + // devices sitting on a SAN or attached through a software-managed + // initiator connection; local disks are covered by the disk list + let sanTransports = { + fc: 1, + sas: 1, + iscsi: 1, + 'nvme-fc': 1, + 'nvme-tcp': 1, + 'nvme-rdma': 1, + 'nvme-loop': 1, + }; + + // hide devices with local or unknown transports unless 'Show all devices' is enabled + let sanTransportFilter = new Ext.util.Filter({ + id: 'san-transport', + filterFn: (rec) => !!sanTransports[rec.get('transport')], + }); + + let store = Ext.create('Ext.data.Store', { + fields: [ + 'devpath', + 'size', + 'transport', + 'vendor', + 'model', + 'serial', + 'wwn', + 'paths', + 'usage', + 'vgname', + 'used-by-storage', + 'detail', + ], + proxy: { + type: 'proxmox', + url: `/api2/json/nodes/${me.nodename}/scan/san-luns`, + }, + filters: [sanTransportFilter], + sorters: 'devpath', + }); + me.store = store; + + me.tbar = [ + { + text: gettext('Reload'), + iconCls: 'fa fa-refresh', + handler: () => me.reload(), + }, + '->', + { + xtype: 'proxmoxcheckbox', + boxLabel: gettext('Show all devices'), + autoEl: { + tag: 'div', + 'data-qtip': gettext('Also list devices with local or unknown transports.'), + }, + listeners: { + change: function (f, value) { + let filters = store.getFilters(); + if (value) { + filters.remove(sanTransportFilter); + } else { + filters.add(sanTransportFilter); + } + }, + }, + }, + ]; + + me.callParent(); + + Proxmox.Utils.monStoreErrors(me, store); + me.reload(); + }, +}); -- 2.47.3