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 84A5A1FF0ED for ; Fri, 31 Jul 2026 17:37:13 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 0DB242164C; Fri, 31 Jul 2026 17:36:40 +0200 (CEST) From: Jakob Klocker To: pve-devel@lists.proxmox.com Subject: [PATCH pve-manager 9/9] www: storage: display custom summary for failed plugins Date: Fri, 31 Jul 2026 17:36:17 +0200 Message-ID: <20260731153617.358265-10-j.klocker@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260731153617.358265-1-j.klocker@proxmox.com> References: <20260731153617.358265-1-j.klocker@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 1 AWL -0.528 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: UKLONUO5FTOVMDJW3HKKELO4QJSX63YK X-Message-ID-Hash: UKLONUO5FTOVMDJW3HKKELO4QJSX63YK X-MailFrom: jklocker@dev.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 CC: Jakob Klocker X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Currently there is only a generic summary for storages, which doesn't mention that a plugin failed to load. Add a dedicated summary view for storages whose plugin failed to load. Signed-off-by: Jakob Klocker --- www/manager6/Makefile | 1 + www/manager6/storage/Browser.js | 33 ++++++--- www/manager6/storage/PluginError.js | 111 ++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 www/manager6/storage/PluginError.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index eb0e9d9c..7eb187e9 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -370,6 +370,7 @@ JSSRC= \ storage/BTRFSEdit.js \ storage/NFSEdit.js \ storage/PBSEdit.js \ + storage/PluginError.js \ storage/RBDEdit.js \ storage/StatusView.js \ storage/Summary.js \ diff --git a/www/manager6/storage/Browser.js b/www/manager6/storage/Browser.js index d0237948..2e3cfb42 100644 --- a/www/manager6/storage/Browser.js +++ b/www/manager6/storage/Browser.js @@ -29,6 +29,30 @@ Ext.define('PVE.storage.Browser', { let plugin = res.plugintype; let isEsxi = plugin === 'esxi'; + let pluginFailed = res.pluginstate === 'load-error'; + + Ext.apply(me, { + title: Ext.String.format( + gettext('Storage {0} on node {1}'), + `'${storeid}'`, + `'${nodename}'`, + ), + hstateid: 'storagetab', + }); + + if (pluginFailed) { + me.items = [ + { + xtype: 'pveStoragePluginError', + title: gettext('Summary'), + iconCls: 'fa fa-book', + itemId: 'summary', + }, + ]; + + me.callParent(); + return; + } me.items = !isEsxi ? [ @@ -43,15 +67,6 @@ Ext.define('PVE.storage.Browser', { let caps = Ext.state.Manager.get('GuiCap'); - Ext.apply(me, { - title: Ext.String.format( - gettext('Storage {0} on node {1}'), - `'${storeid}'`, - `'${nodename}'`, - ), - hstateid: 'storagetab', - }); - if ( caps.storage['Datastore.Allocate'] || caps.storage['Datastore.AllocateSpace'] || diff --git a/www/manager6/storage/PluginError.js b/www/manager6/storage/PluginError.js new file mode 100644 index 00000000..a797631a --- /dev/null +++ b/www/manager6/storage/PluginError.js @@ -0,0 +1,111 @@ +Ext.define('PVE.storage.PluginError', { + extend: 'Ext.panel.Panel', + alias: 'widget.pveStoragePluginError', + + scrollable: true, + bodyPadding: 5, + layout: { type: 'column' }, + defaults: { padding: 5, columnWidth: 1 }, + + initComponent: function () { + let me = this; + + let nodename = me.pveSelNode.data.node; + if (!nodename) { + throw 'no node name specified'; + } + let storage = me.pveSelNode.data.storage; + if (!storage) { + throw 'no storage ID specified'; + } + + let res = me.pveSelNode.data; + let type = res.plugintype || gettext('unknown'); + let content = res.content || '-'; + + let rows = [ + { + xtype: 'pmxInfoWidget', + itemId: 'enabled', + title: gettext('Enabled'), + printBar: false, + }, + { xtype: 'pmxInfoWidget', itemId: 'active', title: gettext('Active'), printBar: false }, + { + xtype: 'pmxInfoWidget', + itemId: 'content', + title: gettext('Content'), + printBar: false, + }, + { xtype: 'pmxInfoWidget', itemId: 'type', title: gettext('Type'), printBar: false }, + { + xtype: 'pmxInfoWidget', + itemId: 'external', + title: gettext('External'), + printBar: false, + }, + { + xtype: 'pmxInfoWidget', + itemId: 'plugstatus', + title: gettext('Plugin Status'), + printBar: false, + }, + { + xtype: 'pmxInfoWidget', + itemId: 'plugin-url', + title: gettext('Plugin URL'), + printBar: false, + hidden: true, + }, + ]; + + let explanation = Ext.String.format( + gettext( + "The plugin for storage type '{0}' could not be loaded on node '{1}', " + + 'so live status and usage are unavailable. Install or update a compatible ' + + 'version of the plugin to restore access.', + ), + Ext.htmlEncode(type), + Ext.htmlEncode(nodename), + ); + + Ext.apply(me, { + items: [ + { + xtype: 'panel', + title: gettext('Status'), + layout: { type: 'vbox', align: 'stretch' }, + bodyPadding: 5, + defaults: { padding: '2 25' }, + items: rows, + listeners: { + afterrender: function (panel) { + panel.down('#enabled').updateValue(gettext('No')); + panel.down('#active').updateValue(gettext('No')); + panel + .down('#content') + .updateValue(PVE.Utils.format_content_types(content)); + panel.down('#type').updateValue(PVE.Utils.format_storage_type(type)); + panel.down('#external').updateValue(gettext('Yes')); + panel.down('#plugstatus').updateValue(gettext('Error loading Plugin')); + + let url = res['plugin-url']; + if (url) { + let row = panel.down('#plugin-url'); + row.updateValue(PVE.Utils.render_plugin_url(url, res.plugintype)); + row.setHidden(false); + } + }, + }, + }, + { + xtype: 'box', + padding: 15, + html: explanation, + }, + ], + }); + + me.callParent(); + }, +}); -- 2.47.3