mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-07-25 05:45:09 +00:00

this is a grid field for selecting multiple pci devices at once, like we need for the mapped pci ui. There we want to be able to select multiple devices such that one gets selected automatically we can select a whole slot here, but that disables selecting the individual functions of that device. Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
289 lines
5.6 KiB
JavaScript
289 lines
5.6 KiB
JavaScript
Ext.define('PVE.form.MultiPCISelector', {
|
|
extend: 'Ext.grid.Panel',
|
|
alias: 'widget.pveMultiPCISelector',
|
|
|
|
emptyText: gettext('No Devices found'),
|
|
|
|
mixins: {
|
|
field: 'Ext.form.field.Field',
|
|
},
|
|
|
|
getValue: function() {
|
|
let me = this;
|
|
return me.value ?? [];
|
|
},
|
|
|
|
getSubmitData: function() {
|
|
let me = this;
|
|
let res = {};
|
|
res[me.name] = me.getValue();
|
|
return res;
|
|
},
|
|
|
|
setValue: function(value) {
|
|
let me = this;
|
|
|
|
value ??= [];
|
|
|
|
me.updateSelectedDevices(value);
|
|
|
|
return me.mixins.field.setValue.call(me, value);
|
|
},
|
|
|
|
getErrors: function() {
|
|
let me = this;
|
|
|
|
let errorCls = ['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid'];
|
|
|
|
if (me.getValue().length < 1) {
|
|
let error = gettext("Must choose at least one device");
|
|
me.addCls(errorCls);
|
|
me.getActionEl()?.dom.setAttribute('data-errorqtip', error);
|
|
|
|
return [error];
|
|
}
|
|
|
|
me.removeCls(errorCls);
|
|
me.getActionEl()?.dom.setAttribute('data-errorqtip', "");
|
|
|
|
return [];
|
|
},
|
|
|
|
viewConfig: {
|
|
getRowClass: function(record) {
|
|
if (record.data.disabled === true) {
|
|
return 'x-item-disabled';
|
|
}
|
|
return '';
|
|
},
|
|
},
|
|
|
|
updateSelectedDevices: function(value = []) {
|
|
let me = this;
|
|
|
|
let recs = [];
|
|
let store = me.getStore();
|
|
|
|
for (const map of value) {
|
|
let parsed = PVE.Parser.parsePropertyString(map);
|
|
if (parsed.node !== me.nodename) {
|
|
continue;
|
|
}
|
|
|
|
let rec = store.getById(parsed.path);
|
|
if (rec) {
|
|
recs.push(rec);
|
|
}
|
|
}
|
|
|
|
me.suspendEvent('change');
|
|
me.setSelection([]);
|
|
me.setSelection(recs);
|
|
me.resumeEvent('change');
|
|
},
|
|
|
|
setNodename: function(nodename) {
|
|
let me = this;
|
|
|
|
if (!nodename || me.nodename === nodename) {
|
|
return;
|
|
}
|
|
|
|
me.nodename = nodename;
|
|
|
|
me.getStore().setProxy({
|
|
type: 'proxmox',
|
|
url: '/api2/json/nodes/' + me.nodename + '/hardware/pci?pci-class-blacklist=',
|
|
});
|
|
|
|
me.setSelection([]);
|
|
|
|
me.getStore().load({
|
|
callback: (recs, op, success) => me.addSlotRecords(recs, op, success),
|
|
});
|
|
},
|
|
|
|
setMdev: function(mdev) {
|
|
let me = this;
|
|
if (mdev) {
|
|
me.getStore().addFilter({
|
|
id: 'mdev-filter',
|
|
property: 'mdev',
|
|
value: '1',
|
|
operator: '=',
|
|
});
|
|
} else {
|
|
me.getStore().removeFilter('mdev-filter');
|
|
}
|
|
},
|
|
|
|
// adds the virtual 'slot' records (e.g. '0000:01:00') to the store
|
|
addSlotRecords: function(records, _op, success) {
|
|
let me = this;
|
|
if (!success) {
|
|
return;
|
|
}
|
|
|
|
let slots = {};
|
|
records.forEach((rec) => {
|
|
let slotname = rec.data.id.slice(0, -2); // remove function
|
|
rec.set('slot', slotname);
|
|
if (slots[slotname] !== undefined) {
|
|
slots[slotname].count++;
|
|
return;
|
|
}
|
|
|
|
slots[slotname] = {
|
|
count: 1,
|
|
};
|
|
|
|
if (rec.data.id.endsWith('.0')) {
|
|
slots[slotname].device = rec.data;
|
|
}
|
|
});
|
|
|
|
let store = me.getStore();
|
|
|
|
for (const [slot, { count, device }] of Object.entries(slots)) {
|
|
if (count === 1) {
|
|
continue;
|
|
}
|
|
store.add(Ext.apply({}, {
|
|
id: slot,
|
|
mdev: undefined,
|
|
device_name: gettext('Pass through all functions as one device'),
|
|
}, device));
|
|
}
|
|
|
|
me.updateSelectedDevices(me.value);
|
|
},
|
|
|
|
selectionChange: function(_grid, selection) {
|
|
let me = this;
|
|
|
|
let ids = {};
|
|
selection
|
|
.filter(rec => rec.data.id.indexOf('.') === -1)
|
|
.forEach((rec) => { ids[rec.data.id] = true; });
|
|
|
|
let to_disable = [];
|
|
|
|
me.getStore().each(rec => {
|
|
let id = rec.data.id;
|
|
rec.set('disabled', false);
|
|
if (id.indexOf('.') === -1) {
|
|
return;
|
|
}
|
|
let slot = id.slice(0, -2); // remove function
|
|
|
|
if (ids[slot]) {
|
|
to_disable.push(rec);
|
|
rec.set('disabled', true);
|
|
}
|
|
});
|
|
|
|
me.suspendEvent('selectionchange');
|
|
me.getSelectionModel().deselect(to_disable);
|
|
me.resumeEvent('selectionchange');
|
|
|
|
me.value = me.getSelection().map((rec) => {
|
|
let res = {
|
|
path: rec.data.id,
|
|
node: me.nodename,
|
|
id: `${rec.data.vendor}:${rec.data.device}`.replace(/0x/g, ''),
|
|
'subsystem-id': `${rec.data.subsystem_vendor}:${rec.data.subsystem_device}`.replace(/0x/g, ''),
|
|
};
|
|
|
|
if (rec.data.iommugroup !== -1) {
|
|
res.iommugroup = rec.data.iommugroup;
|
|
}
|
|
|
|
return PVE.Parser.printPropertyString(res);
|
|
});
|
|
me.checkChange();
|
|
},
|
|
|
|
selModel: {
|
|
type: 'checkboxmodel',
|
|
mode: 'SIMPLE',
|
|
},
|
|
|
|
columns: [
|
|
{
|
|
header: 'ID',
|
|
dataIndex: 'id',
|
|
width: 150,
|
|
},
|
|
{
|
|
header: gettext('IOMMU Group'),
|
|
dataIndex: 'iommugroup',
|
|
renderer: (v, _md, rec) => rec.data.slot === rec.data.id ? '' : v === -1 ? '-' : v,
|
|
width: 50,
|
|
},
|
|
{
|
|
header: gettext('Vendor'),
|
|
dataIndex: 'vendor_name',
|
|
flex: 3,
|
|
},
|
|
{
|
|
header: gettext('Device'),
|
|
dataIndex: 'device_name',
|
|
flex: 6,
|
|
},
|
|
{
|
|
header: gettext('Mediated Devices'),
|
|
dataIndex: 'mdev',
|
|
flex: 1,
|
|
renderer: function(val) {
|
|
return Proxmox.Utils.format_boolean(!!val);
|
|
},
|
|
},
|
|
],
|
|
|
|
listeners: {
|
|
selectionchange: function() {
|
|
this.selectionChange(...arguments);
|
|
},
|
|
},
|
|
|
|
store: {
|
|
fields: [
|
|
'id', 'vendor_name', 'device_name', 'vendor', 'device', 'iommugroup', 'mdev',
|
|
'subsystem_vendor', 'subsystem_device', 'disabled',
|
|
{
|
|
name: 'subsystem-vendor',
|
|
calculate: function(data) {
|
|
return data.subsystem_vendor;
|
|
},
|
|
},
|
|
{
|
|
name: 'subsystem-device',
|
|
calculate: function(data) {
|
|
return data.subsystem_device;
|
|
},
|
|
},
|
|
],
|
|
sorters: [
|
|
{
|
|
property: 'id',
|
|
direction: 'ASC',
|
|
},
|
|
],
|
|
},
|
|
|
|
initComponent: function() {
|
|
let me = this;
|
|
|
|
let nodename = me.nodename;
|
|
me.nodename = undefined;
|
|
|
|
me.callParent();
|
|
|
|
Proxmox.Utils.monStoreErrors(me, me.getStore(), true);
|
|
|
|
me.setNodename(nodename);
|
|
|
|
me.initField();
|
|
},
|
|
});
|