From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id C3F12A1FC7 for ; Fri, 16 Jun 2023 15:05:55 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 60D5B33628 for ; Fri, 16 Jun 2023 15:05:55 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Fri, 16 Jun 2023 15:05:52 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id BE54445B6F for ; Fri, 16 Jun 2023 15:05:51 +0200 (CEST) From: Dominik Csapak To: pve-devel@lists.proxmox.com Date: Fri, 16 Jun 2023 15:05:39 +0200 Message-Id: <20230616130542.199182-20-d.csapak@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20230616130542.199182-1-d.csapak@proxmox.com> References: <20230616130542.199182-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.016 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record T_SCC_BODY_TEXT_LINE -0.01 - Subject: [pve-devel] [PATCH manager v7 12/14] ui: allow configuring pci and usb mapping X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 16 Jun 2023 13:05:55 -0000 uses the new ResourceMapTree to add the CRUD interfaces for the mappings. We add both of them into a single panel, since the datacenter menu already has many entries, and without a proper summary for the group, we cannot really put them in a category Signed-off-by: Dominik Csapak --- depends on the pve-docs patch otherwise the onlineHelp ref is not there www/manager6/Makefile | 2 + www/manager6/dc/Config.js | 46 ++++++++++++++- www/manager6/dc/PCIMapView.js | 106 ++++++++++++++++++++++++++++++++++ www/manager6/dc/USBMapView.js | 98 +++++++++++++++++++++++++++++++ 4 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 www/manager6/dc/PCIMapView.js create mode 100644 www/manager6/dc/USBMapView.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 0cb922d6..2d884f4a 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -174,6 +174,8 @@ JSSRC= \ dc/UserTagAccessEdit.js \ dc/RegisteredTagsEdit.js \ dc/RealmSyncJob.js \ + dc/PCIMapView.js \ + dc/USBMapView.js \ lxc/CmdMenu.js \ lxc/Config.js \ lxc/CreateWizard.js \ diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js index f9f937a5..10a4d83a 100644 --- a/www/manager6/dc/Config.js +++ b/www/manager6/dc/Config.js @@ -274,8 +274,50 @@ Ext.define('PVE.dc.Config', { iconCls: 'fa fa-bar-chart', itemId: 'metricservers', onlineHelp: 'external_metric_server', - }, - { + }); + } + + if (caps.mapping['Mapping.Audit'] || + caps.mapping['Mapping.Use'] || + caps.mapping['Mapping.Modify']) { + me.items.push( + { + xtype: 'container', + onlineHelp: 'resource_mapping', + title: gettext('Resource Mappings'), + itemId: 'resources', + iconCls: 'fa fa-folder-o', + layout: { + type: 'vbox', + align: 'stretch', + multi: true, + }, + scrollable: true, + defaults: { + collapsible: true, + animCollapse: false, + margin: '7 10 3 10', + }, + items: [ + { + collapsible: true, + xtype: 'pveDcPCIMapView', + title: gettext('PCI Devices'), + flex: 1, + }, + { + collapsible: true, + xtype: 'pveDcUSBMapView', + title: gettext('USB Devices'), + flex: 1, + }, + ], + }, + ); + } + + if (caps.dc['Sys.Audit']) { + me.items.push({ xtype: 'pveDcSupport', title: gettext('Support'), itemId: 'support', diff --git a/www/manager6/dc/PCIMapView.js b/www/manager6/dc/PCIMapView.js new file mode 100644 index 00000000..3efa19d8 --- /dev/null +++ b/www/manager6/dc/PCIMapView.js @@ -0,0 +1,106 @@ +Ext.define('pve-resource-pci-tree', { + extend: 'Ext.data.Model', + idProperty: 'internalId', + fields: ['type', 'text', 'path', 'id', 'subsystem-id', 'iommugroup', 'description', 'digest'], +}); + +Ext.define('PVE.dc.PCIMapView', { + extend: 'PVE.tree.ResourceMapTree', + alias: 'widget.pveDcPCIMapView', + + editWindowClass: 'PVE.window.PCIMapEditWindow', + baseUrl: '/cluster/mapping/pci', + mapIconCls: 'pve-itype-icon-pci', + getStatusCheckUrl: (node) => `/nodes/${node}/hardware/pci?pci-class-blacklist=`, + entryIdProperty: 'path', + + checkValidity: function(data, node) { + let me = this; + let ids = {}; + data.forEach((entry) => { + ids[entry.id] = entry; + }); + me.getRootNode()?.cascade(function(rec) { + if (rec.data.node !== node || rec.data.type !== 'map') { + return; + } + + let id = rec.data.path; + if (!id.match(/\.\d$/)) { + id += '.0'; + } + let device = ids[id]; + if (!device) { + rec.set('valid', 0); + rec.set('errmsg', Ext.String.format(gettext("Cannot find PCI id {0}"), id)); + rec.commit(); + return; + } + + + let deviceId = `${device.vendor}:${device.device}`.replace(/0x/g, ''); + let subId = `${device.subsystem_vendor}:${device.subsystem_device}`.replace(/0x/g, ''); + + let toCheck = { + id: deviceId, + 'subsystem-id': subId, + iommugroup: device.iommugroup !== -1 ? device.iommugroup : undefined, + }; + + let valid = 1; + let errors = []; + let errText = gettext("Configuration for {0} not correct ('{1}' != '{2}')"); + for (const [key, validValue] of Object.entries(toCheck)) { + if (`${rec.data[key]}` !== `${validValue}`) { + errors.push(Ext.String.format(errText, key, rec.data[key] ?? '', validValue)); + valid = 0; + } + } + + rec.set('valid', valid); + rec.set('errmsg', errors.join('
')); + rec.commit(); + }); + }, + + store: { + sorters: 'text', + model: 'pve-resource-pci-tree', + data: {}, + }, + + columns: [ + { + xtype: 'treecolumn', + text: gettext('ID/Node/Path'), + dataIndex: 'text', + width: 200, + }, + { + text: gettext('Vendor/Device'), + dataIndex: 'id', + }, + { + text: gettext('Subsystem Vendor/Device'), + dataIndex: 'subsystem-id', + }, + { + text: gettext('IOMMU group'), + dataIndex: 'iommugroup', + }, + { + header: gettext('Status'), + dataIndex: 'valid', + flex: 1, + renderer: 'renderStatus', + }, + { + header: gettext('Comment'), + dataIndex: 'description', + renderer: function(value, _meta, record) { + return value ?? record.data.comment; + }, + flex: 1, + }, + ], +}); diff --git a/www/manager6/dc/USBMapView.js b/www/manager6/dc/USBMapView.js new file mode 100644 index 00000000..953e2425 --- /dev/null +++ b/www/manager6/dc/USBMapView.js @@ -0,0 +1,98 @@ +Ext.define('pve-resource-usb-tree', { + extend: 'Ext.data.Model', + idProperty: 'internalId', + fields: ['type', 'text', 'path', 'id', 'description', 'digest'], +}); + +Ext.define('PVE.dc.USBMapView', { + extend: 'PVE.tree.ResourceMapTree', + alias: 'widget.pveDcUSBMapView', + + editWindowClass: 'PVE.window.USBMapEditWindow', + baseUrl: '/cluster/mapping/usb', + mapIconCls: 'fa fa-usb', + getStatusCheckUrl: (node) => `/nodes/${node}/hardware/usb`, + entryIdProperty: 'id', + + checkValidity: function(data, node) { + let me = this; + let ids = {}; + let paths = {}; + data.forEach((entry) => { + ids[`${entry.vendid}:${entry.prodid}`] = entry; + paths[`${entry.busnum}-${entry.usbpath}`] = entry; + }); + me.getRootNode()?.cascade(function(rec) { + if (rec.data.node !== node || rec.data.type !== 'map') { + return; + } + + let device; + if (rec.data.path) { + device = paths[rec.data.path]; + } + device ??= ids[rec.data.id]; + + if (!device) { + rec.set('valid', 0); + rec.set('errmsg', Ext.String.format(gettext("Cannot find USB device {0}"), rec.data.id)); + rec.commit(); + return; + } + + + let deviceId = `${device.vendid}:${device.prodid}`.replace(/0x/g, ''); + + let toCheck = { + id: deviceId, + }; + + let valid = 1; + let errors = []; + let errText = gettext("Configuration for {0} not correct ('{1}' != '{2}')"); + for (const [key, validValue] of Object.entries(toCheck)) { + if (rec.data[key] !== validValue) { + errors.push(Ext.String.format(errText, key, rec.data[key] ?? '', validValue)); + valid = 0; + } + } + + rec.set('valid', valid); + rec.set('errmsg', errors.join('
')); + rec.commit(); + }); + }, + + store: { + sorters: 'text', + model: 'pve-resource-usb-tree', + data: {}, + }, + + columns: [ + { + xtype: 'treecolumn', + text: gettext('ID/Node/Vendor&Device'), + dataIndex: 'text', + width: 200, + }, + { + text: gettext('Path'), + dataIndex: 'path', + }, + { + header: gettext('Status'), + dataIndex: 'valid', + flex: 1, + renderer: 'renderStatus', + }, + { + header: gettext('Comment'), + dataIndex: 'description', + renderer: function(value, _meta, record) { + return value ?? record.data.comment; + }, + flex: 1, + }, + ], +}); -- 2.30.2