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 4B1FD1FF0ED for ; Fri, 31 Jul 2026 17:37:05 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id A803D21638; Fri, 31 Jul 2026 17:36:39 +0200 (CEST) From: Jakob Klocker To: pve-devel@lists.proxmox.com Subject: [PATCH pve-manager 7/9] www: storage: show plugin state/external/url in StatusView Date: Fri, 31 Jul 2026 17:36:15 +0200 Message-ID: <20260731153617.358265-8-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.541 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: 2JLWK67KZXBSNAHGGG3T7BLCEPQMH4FN X-Message-ID-Hash: 2JLWK67KZXBSNAHGGG3T7BLCEPQMH4FN 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: Display additional storage information: `Plugin State`, `External` and `Plugin URL`. The `Plugin URL` row is hidden when no URL is set, and third-party plugin URLs are prefixed to distinguish them from official documentation links. The `Plugin State` row shows whether the plugin is up to date, outdated (with how many more API versions it will remain supported), or failed to load. Signed-off-by: Jakob Klocker --- www/manager6/Utils.js | 68 ++++++++++++++++++++++++++++++ www/manager6/storage/StatusView.js | 38 ++++++++++++++++- 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index 040b5ae0..2f098ff0 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -1072,6 +1072,32 @@ Ext.define('PVE.Utils', { .join(', '); }, + format_plugin_state: function (value) { + if (!value) { + return ''; + } + switch (value.state) { + case 'load-error': + return gettext('Failed to load'); + case 'outdated': { + let ver = value['api-version']; + let min = value['api-min']; + let max = value['api-max']; + let headroom = ver - min; + return Ext.String.format( + gettext( + 'Outdated (API {0}, current {1}) - supported for {2} more API version(s)', + ), + ver, + max, + headroom, + ); + } + default: + return gettext('Up to date'); + } + }, + render_storage_content: function (value, metaData, record) { let data = record.data; let result; @@ -1367,6 +1393,48 @@ Ext.define('PVE.Utils', { return Ext.htmlEncode(Proxmox.Utils.format_task_description(type, id)); }, + render_plugin_url: function (value, type) { + if (value === undefined || value === null || value === '') { + return ''; + } + let isLink = /^https?:\/\//i.test(value); + if (!isLink) { + return Ext.htmlEncode(value); + } + + let isOfficial = false; + + try { + isOfficial = new URL(value).hostname === 'pve.proxmox.com'; + } catch { + isOfficial = false; + } + + let encoded = Ext.htmlEncode(value); + + if (isOfficial) { + let name = type ? PVE.Utils.format_storage_type(type) : ''; + let label = name + ? Ext.String.format(gettext('{0} Documentation'), name) + : gettext('Documentation'); + return ( + '' + + Ext.htmlEncode(label) + + '' + ); + } + + let link = + '' + + encoded + + ''; + return gettext('(External) ') + link; + }, + render_optional_url: function (value) { if (value && value.match(/^https?:\/\//)) { return '' + value + ''; diff --git a/www/manager6/storage/StatusView.js b/www/manager6/storage/StatusView.js index 0525fbb5..ba5e6b07 100644 --- a/www/manager6/storage/StatusView.js +++ b/www/manager6/storage/StatusView.js @@ -2,7 +2,7 @@ Ext.define('PVE.storage.StatusView', { extend: 'Proxmox.panel.StatusView', alias: 'widget.pveStorageStatusView', - height: 230, + height: 320, title: gettext('Status'), layout: { @@ -47,6 +47,42 @@ Ext.define('PVE.storage.StatusView', { textField: 'type', renderer: PVE.Utils.format_storage_type, }, + { + itemId: 'plugin-external', + title: gettext('External'), + printBar: false, + textField: 'plugin', + renderer: (value) => Proxmox.Utils.format_boolean(value?.external), + }, + { + itemId: 'plugin-status', + title: gettext('Plugin State'), + printBar: false, + textField: 'plugin', + renderer: (value) => PVE.Utils.format_plugin_state(value), + }, + { + itemId: 'plugin-url', + title: gettext('Plugin URL'), + printBar: false, + textField: 'plugin', + hidden: true, + renderer: function (value) { + let url = value?.url; + let has = url !== undefined && url !== null && url !== ''; + this.setHidden(!has); + if (!has) { + return ''; + } + let view = this.up('pveStorageStatusView'); + let type; + if (view && view.rstore) { + let rec = view.rstore.getById('type'); + type = rec ? rec.data.value : undefined; + } + return PVE.Utils.render_plugin_url(url, type); + }, + }, { xtype: 'box', height: 10, -- 2.47.3