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 CFD491FF0ED for ; Fri, 31 Jul 2026 12:24:32 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 85AEE217B9; Fri, 31 Jul 2026 12:22:09 +0200 (CEST) From: Dietmar Maurer To: pve-devel@lists.proxmox.com Subject: [RFC pve-manager 24/27] ui: san luns: show multipath path state Date: Fri, 31 Jul 2026 12:21:53 +0200 Message-ID: <20260731102156.3947857-25-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.191 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: KWT2KDFS6KWW7SBX4N3EUF6R7CEWTZ4C X-Message-ID-Hash: KWT2KDFS6KWW7SBX4N3EUF6R7CEWTZ4C 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: Render the active path count next to the total and prepend a warning icon when they differ, so a degraded multipath device stands out. A row expander shows the member paths with their state and, depending on the multipath stack, checker state and WWPNs (multipathd) or ANA state and controller address (native NVMe multipath), plus the fault counter, which would otherwise require "multipath -ll" or digging through sysfs on the CLI. The FC storage wizard LUN grid picks up the degraded hint through the shared renderer. Signed-off-by: Dietmar Maurer --- www/manager6/Utils.js | 12 +++++- www/manager6/node/SANLuns.js | 74 ++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index 5972e1aa..a34e1363 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -1102,7 +1102,17 @@ Ext.define('PVE.Utils', { // only multipath mapped devices report their path count let paths = rec.get('paths'); if (paths) { - text += ` (${paths} ${ngettext('path', 'paths', paths)})`; + let active = rec.get('active-paths'); + if (Ext.isNumeric(active) && active < paths) { + let qtip = Ext.htmlEncode( + gettext('Some paths failed, the multipath device is degraded'), + ); + text = + ` ` + + `${text} (${active}/${paths} ${gettext('paths')})`; + } else { + text += ` (${paths} ${ngettext('path', 'paths', paths)})`; + } } return text; }, diff --git a/www/manager6/node/SANLuns.js b/www/manager6/node/SANLuns.js index b2b9e24d..ce11c39b 100644 --- a/www/manager6/node/SANLuns.js +++ b/www/manager6/node/SANLuns.js @@ -15,6 +15,77 @@ Ext.define('PVE.node.SANLunList', { trackOver: false, }, + plugins: [ + { + ptype: 'rowexpander', + expandOnDblClick: false, + scrollIntoViewOnExpand: false, + rowBodyTpl: [ + '{[this.renderDetails(values)]}', + { + renderDetails: function (values) { + let enc = Ext.htmlEncode; + let lines = []; + if (values.wwn) { + lines.push(`WWN: ${enc(values.wwn)}`); + } + if (values.serial) { + lines.push(`${gettext('Serial')}: ${enc(values.serial)}`); + } + let faults = values['path-faults']; + if (Ext.isNumeric(faults)) { + lines.push( + `${gettext('Path Failures Since Map Creation')}: ${enc(faults)}`, + ); + } + let paths = values['path-list'] || []; + if (paths.length) { + // multipathd paths report checker state and WWPNs, NVMe + // paths their ANA state and controller address + let goodStates = { active: 1, live: 1 }; + let goodAna = { optimized: 1, 'non-optimized': 1 }; + let columns = [ + { key: 'device', label: gettext('Path') }, + { + key: 'state', + label: gettext('State'), + bad: (path) => !goodStates[path.state], + }, + { key: 'checker-state', label: gettext('Checker') }, + { + key: 'ana-state', + label: 'ANA', + bad: (path) => !goodAna[path['ana-state']], + }, + { key: 'host-wwpn', label: 'Host WWPN' }, + { key: 'target-wwpn', label: 'Target WWPN' }, + { key: 'address', label: gettext('Address') }, + ].filter((col) => paths.some((path) => path[col.key] !== undefined)); + let header = columns + .map((col) => `${col.label}`) + .join(''); + let rows = paths.map((path) => { + let cells = columns.map((col) => { + let text = enc(path[col.key] ?? ''); + if (text !== '' && col.bad && col.bad(path)) { + text = ` ${text}`; + } + return text; + }); + return `${cells.join('')}`; + }); + lines.push( + `` + + `${header}${rows.join('')}
`, + ); + } + return lines.join('
'); + }, + }, + ], + }, + ], + columns: [ { header: gettext('Device'), @@ -105,6 +176,9 @@ Ext.define('PVE.node.SANLunList', { 'serial', 'wwn', 'paths', + 'active-paths', + 'path-faults', + 'path-list', 'usage', 'vgname', 'used-by-storage', -- 2.47.3