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 3D17B1FF0AF for ; Fri, 06 Nov 2026 01:58:47 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 9458D21E50; Fri, 06 Nov 2026 01:51:21 +0100 (CET) From: Thomas Lamprecht To: pve-devel@lists.proxmox.com Subject: [PATCH v3 manager 68/76] ui: storage wizard: iscsi: show cluster initiator names Date: Fri, 6 Nov 2026 00:31:43 +0100 Message-ID: <20261105233336.2178064-69-t.lamprecht@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261105233336.2178064-1-t.lamprecht@proxmox.com> References: <20261105233336.2178064-1-t.lamprecht@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1793926182260 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.298 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) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: TJQR35TPNOKO7YMIKZRO7UBW3IW2YUQ3 X-Message-ID-Hash: TJQR35TPNOKO7YMIKZRO7UBW3IW2YUQ3 X-MailFrom: t.lamprecht@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: Show initiator names on the Target step so administrators can configure access rules without reading /etc/iscsi/initiatorname.iscsi on each node. Use node status reports and offer copying one name per line. Include all cluster nodes, marking missing or stale reports, because storage node restrictions are chosen later. Signed-off-by: Thomas Lamprecht --- www/manager6/Makefile | 1 + www/manager6/storage/wizard/IScsi.js | 7 + www/manager6/storage/wizard/NodeIdentities.js | 123 ++++++++++++++++++ www/manager6/storage/wizard/SanLvm.js | 1 + 4 files changed, 132 insertions(+) create mode 100644 www/manager6/storage/wizard/NodeIdentities.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index a90d5e0fd..cd64e2366 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -389,6 +389,7 @@ JSSRC= \ storage/wizard/CommonSettings.js \ storage/wizard/LunPicker.js \ storage/wizard/LunSelect.js \ + storage/wizard/NodeIdentities.js \ storage/wizard/SanLvm.js \ storage/wizard/NFS.js \ storage/wizard/CIFS.js \ diff --git a/www/manager6/storage/wizard/IScsi.js b/www/manager6/storage/wizard/IScsi.js index fb4decb38..57cbe3243 100644 --- a/www/manager6/storage/wizard/IScsi.js +++ b/www/manager6/storage/wizard/IScsi.js @@ -34,6 +34,13 @@ Ext.define('PVE.storage.wizard.IScsiTarget', { }, ]; + me.columnB = [ + { + xtype: 'pveStorageWizardNodeIdentities', + kind: 'iscsi', + }, + ]; + me.column2 = []; if (!PVE.Utils.isStandaloneNode()) { me.column2.push({ diff --git a/www/manager6/storage/wizard/NodeIdentities.js b/www/manager6/storage/wizard/NodeIdentities.js new file mode 100644 index 000000000..21b89900a --- /dev/null +++ b/www/manager6/storage/wizard/NodeIdentities.js @@ -0,0 +1,123 @@ +Ext.define('PVE.storage.wizard.NodeIdentities', { + extend: 'Ext.grid.Panel', + xtype: 'pveStorageWizardNodeIdentities', + + kind: 'iscsi', + + // five nodes fit without scrolling + height: 200, + disableSelection: true, + + sources: { + iscsi: { + url: '/cluster/multipath/status', + field: 'iscsi-initiator-name', + header: gettext('Initiator Name'), + allowText: gettext( + 'The target must allow the initiator names of the nodes that use this storage.', + ), + copyTip: gettext('Copy the known initiator names, one per line'), + }, + }, + + getIdentity: function (node) { + return this.getStore().findRecord('node', node, 0, false, true, true)?.get('identity'); + }, + + load: function () { + let me = this; + let source = me.sources[me.kind]; + Proxmox.Utils.API2Request({ + url: source.url, + method: 'GET', + failure: (response) => + !me.destroyed && Proxmox.Utils.setErrorMask(me, response.htmlStatus), + success: function (response) { + if (me.destroyed) { + return; + } + Proxmox.Utils.setErrorMask(me, false); + let reports = response.result.data?.nodes || {}; + // every node, the nodes of the storage are only chosen on a later step + let nodes = new Set([ + ...PVE.data.ResourceStore.getNodes().map((node) => node.node), + ...Object.keys(reports), + ]); + me.getStore().setData( + [...nodes].sort().map((node) => { + let report = reports[node]; + let current = report && !['offline', 'stale'].includes(report.reason); + return { + node, + identity: report?.[source.field], + note: !report + ? gettext('No report') + : !current + ? PVE.SANUtils.render_multipath_reason(report.reason) + : undefined, + }; + }), + ); + }, + }); + }, + + initComponent: function () { + let me = this; + let source = me.sources[me.kind]; + + me.store = Ext.create('Ext.data.Store', { + fields: ['node', 'identity', 'note'], + }); + + me.columns = [ + { + header: gettext('Node'), + dataIndex: 'node', + width: 120, + }, + { + header: source.header, + dataIndex: 'identity', + flex: 1, + renderer: function (value, metaData, rec) { + let note = rec.get('note'); + if (!value) { + return `${Ext.htmlEncode(note ?? Proxmox.Utils.unknownText)}`; + } + if (note) { + let qtip = Ext.htmlEncode(note); + return ` ${Ext.htmlEncode(value)}`; + } + return Ext.htmlEncode(value); + }, + }, + ]; + + me.tbar = [ + { + xtype: 'displayfield', + value: source.allowText, + }, + '->', + { + text: gettext('Copy'), + iconCls: 'fa fa-clipboard', + tooltip: source.copyTip, + handler: function () { + let identities = me + .getStore() + .getRange() + .map((rec) => rec.get('identity')) + .filter((identity) => !!identity); + if (identities.length) { + PVE.Utils.copyToClipboard(identities.join('\n')); + } + }, + }, + ]; + + me.callParent(); + me.load(); + }, +}); diff --git a/www/manager6/storage/wizard/SanLvm.js b/www/manager6/storage/wizard/SanLvm.js index 657b75e45..85939c3b2 100644 --- a/www/manager6/storage/wizard/SanLvm.js +++ b/www/manager6/storage/wizard/SanLvm.js @@ -72,6 +72,7 @@ PVE.storage.wizard.submitSanLvm = function (wizard, values, transport) { ), node: node, storage: baseId, + initiator: wizard.down('pveStorageWizardNodeIdentities')?.getIdentity(node), }); if (active && wizard.canSubmit()) { selected = wizard.lunView.getSelection(); -- 2.47.3