dc/UserTagAccessEdit.js \
dc/RegisteredTagsEdit.js \
dc/RealmSyncJob.js \
+ dc/PCIMapView.js \
+ dc/USBMapView.js \
lxc/CmdMenu.js \
lxc/Config.js \
lxc/CreateWizard.js \
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',
--- /dev/null
+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,
+ },
+ ],
+});
--- /dev/null
+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,
+ },
+ ],
+});