mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-07-17 16:30:07 +00:00

they got lost in my last rebase/refactor. the onLoadCallBack is used to check by the window if there are iommu groups at all, and the checkIsolated function checks if the selected ones are in a separate group (in regards to the other devices) Signed-off-by: Dominik Csapak <d.csapak@proxmox.com> Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
302 lines
5.9 KiB
JavaScript
302 lines
5.9 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',
|
|
},
|
|
|
|
// will be called after loading finished
|
|
onLoadCallBack: Ext.emptyFn,
|
|
|
|
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');
|
|
}
|
|
me.setSelection();
|
|
},
|
|
|
|
// 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
|
|
if (slots[slotname] !== undefined) {
|
|
slots[slotname].count++;
|
|
rec.set('slot', slots[slotname]);
|
|
return;
|
|
}
|
|
slots[slotname] = {
|
|
count: 1,
|
|
};
|
|
|
|
rec.set('slot', slots[slotname]);
|
|
|
|
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',
|
|
renderer: function(value, _md, rec) {
|
|
if (value.match(/\.[0-9a-f]/i) && rec.data.slot?.count > 1) {
|
|
return ` ${value}`;
|
|
}
|
|
return value;
|
|
},
|
|
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();
|
|
|
|
me.mon(me.getStore(), 'load', me.onLoadCallBack);
|
|
|
|
Proxmox.Utils.monStoreErrors(me, me.getStore(), true);
|
|
|
|
me.setNodename(nodename);
|
|
|
|
me.initField();
|
|
},
|
|
});
|