all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Thomas Lamprecht <t.lamprecht@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH manager 13/13] ui: node: show multipath maps and their paths under Disks
Date: Fri, 26 Jun 2026 14:07:43 +0200	[thread overview]
Message-ID: <20260626121000.2095591-14-t.lamprecht@proxmox.com> (raw)
In-Reply-To: <20260626121000.2095591-1-t.lamprecht@proxmox.com>

Add a read-only per-node view of the device-mapper multipath maps and
their individual paths, the detail behind the datacenter health matrix.
Each map expands to its paths with their device, state and transport, so
when the matrix flags a node as degraded an operator can see exactly
which path is down without dropping to the shell.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
---
 www/manager6/Makefile          |   1 +
 www/manager6/node/Config.js    |   7 ++
 www/manager6/node/Multipath.js | 163 +++++++++++++++++++++++++++++++++
 3 files changed, 171 insertions(+)
 create mode 100644 www/manager6/node/Multipath.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 1a3b845c8..9b5125a66 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -245,6 +245,7 @@ JSSRC= 							\
 	node/Directory.js				\
 	node/LVM.js					\
 	node/LVMThin.js					\
+	node/Multipath.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 f6cd87492..29a6b9a77 100644
--- a/www/manager6/node/Config.js
+++ b/www/manager6/node/Config.js
@@ -355,6 +355,13 @@ Ext.define('PVE.node.Config', {
                     groups: ['storage'],
                     xtype: 'pveZFSList',
                 },
+                {
+                    xtype: 'pveMultipathStatus',
+                    title: gettext('Multipath'),
+                    itemId: 'multipath',
+                    iconCls: 'fa fa-road',
+                    groups: ['storage'],
+                },
                 {
                     xtype: 'pveNodeCephStatus',
                     title: 'Ceph',
diff --git a/www/manager6/node/Multipath.js b/www/manager6/node/Multipath.js
new file mode 100644
index 000000000..51c257d42
--- /dev/null
+++ b/www/manager6/node/Multipath.js
@@ -0,0 +1,163 @@
+Ext.define('PVE-node-multipath-map', {
+    extend: 'Ext.data.Model',
+    idProperty: 'wwid',
+    fields: [
+        'wwid',
+        'name',
+        'health',
+        'transport',
+        'used-by',
+        'path-groups',
+        { name: 'paths-active', type: 'number' },
+        { name: 'paths-total', type: 'number' },
+        { name: 'size', type: 'number' },
+        {
+            // pre-rendered path detail for the row expander; keeps the hyphenated map keys out
+            // of the XTemplate and recomputes when the paths change
+            name: 'pathsHtml',
+            calculate: function (data) {
+                let html = '';
+                (data['path-groups'] || []).forEach((pg) => {
+                    (pg.paths || []).forEach((p) => {
+                        let state = p['dm-state'] || '';
+                        let icon = state === 'active' ? 'check-circle good' : 'times-circle critical';
+                        let extra = [p['dev-state'], p.transport].filter((v) => v).join(', ');
+                        html +=
+                            '<div style="padding:1px 0;">'
+                            + `<i class="fa fa-fw fa-${icon}"></i> `
+                            + `${Ext.htmlEncode(p.dev || '')} - ${Ext.htmlEncode(state)}`
+                            + (extra ? ` (${Ext.htmlEncode(extra)})` : '')
+                            + '</div>';
+                    });
+                });
+                return html;
+            },
+        },
+    ],
+});
+
+// Read-only per-node view of the device-mapper multipath maps and their paths, the detail behind
+// the datacenter-wide health matrix. The map rows update in place via a DiffStore, so live
+// path-state changes do not flicker or collapse the expanded rows.
+Ext.define('PVE.node.MultipathStatus', {
+    extend: 'Ext.grid.Panel',
+    xtype: 'pveMultipathStatus',
+
+    stateful: true,
+    stateId: 'grid-node-multipath',
+
+    emptyText: gettext('No multipath maps on this node.'),
+
+    plugins: [
+        {
+            ptype: 'rowexpander',
+            rowBodyTpl: ['<div style="padding:2px 0 6px 36px;">{pathsHtml}</div>'],
+        },
+    ],
+
+    columns: [
+        {
+            text: gettext('Name'),
+            dataIndex: 'name',
+            flex: 1,
+            renderer: (v, meta, rec) => v || rec.data.wwid,
+        },
+        {
+            text: 'WWID',
+            dataIndex: 'wwid',
+            flex: 1,
+        },
+        {
+            text: gettext('Health'),
+            dataIndex: 'health',
+            width: 130,
+            renderer: PVE.Utils.render_multipath_health,
+        },
+        {
+            text: gettext('Paths'),
+            width: 90,
+            align: 'right',
+            renderer: (v, meta, rec) => `${rec.data['paths-active']}/${rec.data['paths-total']}`,
+        },
+        {
+            text: gettext('Transport'),
+            dataIndex: 'transport',
+            width: 90,
+            renderer: (v) => v || '',
+        },
+        {
+            text: gettext('Size'),
+            dataIndex: 'size',
+            width: 100,
+            align: 'right',
+            renderer: (v) => (v ? Proxmox.Utils.format_size(v) : ''),
+        },
+        {
+            text: gettext('Used by'),
+            dataIndex: 'used-by',
+            flex: 1,
+            renderer: (v) => v || '',
+        },
+    ],
+
+    initComponent: function () {
+        let me = this;
+
+        me.nodename = me.pveSelNode.data.node;
+        if (!me.nodename) {
+            throw 'no node name specified';
+        }
+
+        let rstore = Ext.create('Proxmox.data.UpdateStore', {
+            interval: 3000,
+            storeid: `node-multipath-${me.nodename}`,
+            model: 'PVE-node-multipath-map',
+            proxy: {
+                type: 'proxmox',
+                url: `/api2/json/nodes/${me.nodename}/disks/multipath`,
+                // the endpoint wraps the maps in a supported/running envelope
+                reader: { rootProperty: 'data.maps' },
+            },
+        });
+
+        me.store = Ext.create('Proxmox.data.DiffStore', {
+            rstore: rstore,
+            sorters: [{ property: 'wwid' }],
+        });
+
+        me.tbar = [
+            {
+                text: gettext('Reload'),
+                iconCls: 'fa fa-refresh',
+                handler: () => rstore.load(),
+            },
+        ];
+
+        me.callParent();
+
+        Proxmox.Utils.monStoreErrors(me, rstore, true);
+
+        // the empty grid means different things (no maps, daemon down, not installed); pull the
+        // reason from the envelope to guide the operator
+        me.mon(rstore, 'load', function (store, records, success) {
+            if (!success) {
+                return;
+            }
+            let raw = rstore.getProxy().getReader().rawData || {};
+            let info = raw.data || raw;
+            let text;
+            if (!info.supported) {
+                text = gettext('multipath-tools is not installed on this node.');
+            } else if (!info.running) {
+                text = gettext('The multipathd daemon is not running.');
+            } else {
+                text = gettext('No multipath maps on this node.');
+            }
+            me.setEmptyText(text);
+        });
+
+        me.on('activate', () => rstore.startUpdate());
+        me.on('deactivate', () => rstore.stopUpdate());
+        me.on('destroy', () => rstore.stopUpdate());
+    },
+});
-- 
2.47.3





      parent reply	other threads:[~2026-06-26 12:11 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-26 12:07 [PATCH storage,cluster,manager 0/13] multipath: cluster-wide config, storage and health overview Thomas Lamprecht
2026-06-26 12:07 ` [PATCH storage 01/13] multipath: add helper library and managed configuration Thomas Lamprecht
2026-06-26 14:43   ` Maximiliano Sandoval
2026-06-26 12:07 ` [PATCH storage 02/13] api: disks: add read-only multipath status endpoint Thomas Lamprecht
2026-06-26 12:07 ` [PATCH storage 03/13] api: multipath: add cluster-wide configuration endpoints Thomas Lamprecht
2026-06-26 12:07 ` [PATCH storage 04/13] multipath: add storage plugin for multipath LUNs Thomas Lamprecht
2026-06-26 12:07 ` [PATCH storage 05/13] lvm: allow a multipath storage as the base device Thomas Lamprecht
2026-06-26 12:07 ` [PATCH storage 06/13] multipath: broadcast per-node map health to the cluster KV store Thomas Lamprecht
2026-06-26 12:07 ` [PATCH storage 07/13] api: multipath: add cluster-wide health status endpoint Thomas Lamprecht
2026-06-26 12:07 ` [PATCH cluster 08/13] pmxcfs: track cluster-wide multipath configuration Thomas Lamprecht
2026-06-26 12:07 ` [PATCH manager 09/13] pvestatd: apply the cluster-wide multipath config on each node Thomas Lamprecht
2026-06-26 12:07 ` [PATCH manager 10/13] api: cluster: mount the multipath configuration endpoint Thomas Lamprecht
2026-06-26 12:07 ` [PATCH manager 11/13] pvestatd: broadcast multipath map health to the cluster Thomas Lamprecht
2026-06-26 12:07 ` [PATCH manager 12/13] ui: dc: add multipath health matrix and config editor Thomas Lamprecht
2026-06-26 14:05   ` Maximiliano Sandoval
2026-06-26 12:07 ` Thomas Lamprecht [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260626121000.2095591-14-t.lamprecht@proxmox.com \
    --to=t.lamprecht@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal