mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-07-24 04:27:33 +00:00
ui: allow configuring pci and usb mapping
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>
This commit is contained in:
parent
386f8d9795
commit
70eb18e8f0
@ -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 \
|
||||
|
@ -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',
|
||||
|
106
www/manager6/dc/PCIMapView.js
Normal file
106
www/manager6/dc/PCIMapView.js
Normal file
@ -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,
|
||||
},
|
||||
],
|
||||
});
|
98
www/manager6/dc/USBMapView.js
Normal file
98
www/manager6/dc/USBMapView.js
Normal file
@ -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,
|
||||
},
|
||||
],
|
||||
});
|
Loading…
Reference in New Issue
Block a user