]> git.proxmox.com Git - pve-manager.git/commitdiff
ui: allow configuring pci and usb mapping
authorDominik Csapak <d.csapak@proxmox.com>
Fri, 16 Jun 2023 13:05:39 +0000 (15:05 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Fri, 16 Jun 2023 14:25:42 +0000 (16:25 +0200)
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 <d.csapak@proxmox.com>
www/manager6/Makefile
www/manager6/dc/Config.js
www/manager6/dc/PCIMapView.js [new file with mode: 0644]
www/manager6/dc/USBMapView.js [new file with mode: 0644]

index 0cb922d6095aaa70cfcbb0f4ec1086ebcf2a6ade..2d884f4a4aef3255a76e95b76b47bc9ca5dc4f02 100644 (file)
@@ -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                             \
index f9f937a5550df9b65fc260a85e857f29011f12a0..10a4d83ae0e3aa6bc2729d6eafead1e5b06ec113 100644 (file)
@@ -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 (file)
index 0000000..3efa19d
--- /dev/null
@@ -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('<br>'));
+           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 (file)
index 0000000..953e242
--- /dev/null
@@ -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('<br>'));
+           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,
+       },
+    ],
+});