From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id D10341FF09B for ; Mon, 31 Aug 2026 14:31:20 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 2089E216B5; Mon, 31 Aug 2026 14:30:05 +0200 (CEST) From: Dominik Csapak To: pve-devel@lists.proxmox.com Subject: [PATCH manager v4 07/12] ui: qemu: introduce hardware disk grid Date: Mon, 31 Aug 2026 14:27:42 +0200 Message-ID: <20260831122953.2980232-8-d.csapak@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260831122953.2980232-1-d.csapak@proxmox.com> References: <20260831122953.2980232-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.565 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) PROLO_LEO1 0.1 Meta Catches all Leo drug variations so far 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: JLCZ3QUCVUOOH3X2D6PR7ZJYZW3ONDVK X-Message-ID-Hash: JLCZ3QUCVUOOH3X2D6PR7ZJYZW3ONDVK X-MailFrom: d.csapak@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: intended to be used inside the hardware view. not used yet. The toolbar buttons will be added directly in the hardware view since it requires accessing some data from there. Signed-off-by: Dominik Csapak --- www/manager6/Makefile | 1 + www/manager6/Utils.js | 7 + www/manager6/qemu/HardwareDiskGrid.js | 213 ++++++++++++++++++++++++++ 3 files changed, 221 insertions(+) create mode 100644 www/manager6/qemu/HardwareDiskGrid.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 79ec91bd..01715aa0 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -270,6 +270,7 @@ JSSRC= \ qemu/HDTPM.js \ qemu/HDMove.js \ qemu/HDResize.js \ + qemu/HardwareDiskGrid.js \ qemu/HardwareView.js \ qemu/IPConfigEdit.js \ qemu/KeyboardEdit.js \ diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index e9524b11..f48748db 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -2168,6 +2168,13 @@ Ext.define('PVE.Utils', { ); return Ext.htmlEncode(description); }, + + renderBooleanProperty: function (value) { + if (Ext.isString(value)) { + value = PVE.Parser.parseBoolean(value); + } + return Proxmox.Utils.format_boolean(value); + }, }, singleton: true, diff --git a/www/manager6/qemu/HardwareDiskGrid.js b/www/manager6/qemu/HardwareDiskGrid.js new file mode 100644 index 00000000..f64a38e8 --- /dev/null +++ b/www/manager6/qemu/HardwareDiskGrid.js @@ -0,0 +1,213 @@ +Ext.define('PVE.qemu.HardwareDiskGrid', { + extend: 'PVE.grid.PendingGrid', + alias: 'widget.pveHardwareDiskGrid', + + title: gettext('Hard Disks'), + + stateful: true, + stateId: 'pve-qemu-hardware-disk', + + border: false, + expandable: false, + + minHeight: 100, + emptyText: gettext('No hard disks configured'), + + recordParser: PVE.Parser.parseQemuDrive, + // override to render the keys properly + renderKey: Ext.htmlEncode, + + columns: [ + { + header: gettext('Bus/Device'), + dataIndex: 'id', + width: 150, + renderer: 'renderKey', + }, + { + header: gettext('Volume/File'), + dataIndex: 'file', + flex: 1, + minWidth: 200, + }, + { + header: gettext('Size'), + dataIndex: 'size', + width: 100, + renderer: (value) => { + if (value === undefined || value === null) { + return ''; + } + + let size = value; + if (!size.toString().slice(-1).match(/[0-9]/)) { + // IEC unit is implicit in config, but parser expects it as 'i' suffix + size = `${value}i`; + } + + return Proxmox.Utils.autoscale_size_unit(size); + }, + }, + { + header: gettext('Secure Boot (EFI Disk only)'), + dataIndex: 'pre-enrolled-keys', + renderer: (v, _md, rec, rI, cI, store, view, pending) => { + let iface = pending ? rec.data.pending?.interface : rec.data.interface; + if (iface !== 'efidisk') { + return ''; + } + if (v) { + let msCerts = pending ? rec.data.pending?.['ms-cert'] : rec.data['ms-cert']; + if (msCerts) { + return `${gettext('Yes')} (MS Certificate: ${msCerts})`; + } else { + return gettext('Yes'); + } + } else { + return gettext('No'); + } + }, + }, + { + header: gettext('Cache'), + dataIndex: 'cache', + width: 140, + renderer: (v) => v ?? `${Proxmox.Utils.defaultText} (${gettext('No cache')})`, + }, + { + header: gettext('Discard'), + dataIndex: 'discard', + width: 80, + renderer: PVE.Utils.renderBooleanProperty, + }, + { + header: gettext('IO thread'), + dataIndex: 'iothread', + width: 100, + renderer: PVE.Utils.renderBooleanProperty, + }, + { + header: gettext('SSD emulation'), + dataIndex: 'ssd', + width: 120, + renderer: PVE.Utils.renderBooleanProperty, + }, + { + header: gettext('Backup'), + dataIndex: 'backup', + width: 80, + renderer: (v) => PVE.Utils.renderBooleanProperty(v ?? true), + }, + { + header: gettext('Read-Only'), + dataIndex: 'ro', + width: 100, + renderer: PVE.Utils.renderBooleanProperty, + }, + { + header: gettext('Replicate'), + dataIndex: 'replicate', + width: 100, + renderer: PVE.Utils.renderBooleanProperty, + }, + { + header: gettext('Async I/O'), + dataIndex: 'aio', + width: 100, + }, + { + header: gettext('Bandwidth Limits'), + hidden: true, + columns: [ + { + header: gettext('Read limit (MiB/s)'), + dataIndex: 'mbps_rd', + width: 110, + }, + { + header: gettext('Write limit (MiB/s)'), + dataIndex: 'mbps_wr', + width: 110, + }, + { + header: gettext('Read limit (ops/s)'), + dataIndex: 'iops_rd', + width: 110, + }, + { + header: gettext('Write limit (ops/s)'), + dataIndex: 'iops_wr', + width: 110, + }, + { + header: gettext('Read Burst (MiB/s)'), + dataIndex: 'mbps_rd_max', + width: 110, + }, + { + header: gettext('Write Burst (MiB/s)'), + dataIndex: 'mbps_wr_max', + width: 110, + }, + { + header: gettext('Read Burst (ops/s)'), + dataIndex: 'iops_rd_max', + width: 110, + }, + { + header: gettext('Write Burst (ops/s)'), + dataIndex: 'iops_wr_max', + width: 110, + }, + ], + }, + { + header: gettext('Detect Zeroes'), + dataIndex: 'detect_zeroes', + hidden: true, + renderer: PVE.Utils.renderBooleanProperty, + }, + { + header: gettext('Vendor'), + dataIndex: 'vendor', + hidden: true, + }, + { + header: gettext('Product'), + dataIndex: 'product', + hidden: true, + }, + { + header: gettext('Serial'), + dataIndex: 'serial', + hidden: true, + }, + { + header: gettext('Queues'), + dataIndex: 'queues', + hidden: true, + }, + { + header: gettext('Shared'), + dataIndex: 'shared', + hidden: true, + renderer: PVE.Utils.renderBooleanProperty, + }, + { + header: gettext('Write Error Action'), + dataIndex: 'werror', + hidden: true, + }, + { + header: 'WWN', + dataIndex: 'wwn', + hidden: true, + }, + { + header: gettext('Raw Config'), + dataIndex: 'raw', + hidden: true, + flex: 1, + }, + ], +}); -- 2.47.3