ui: form: add MultiPCISelector
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>
This commit is contained in:
parent
f7b5386a80
commit
02adfe1727
@ -700,3 +700,7 @@ table.osds td:first-of-type {
|
||||
cursor: pointer;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.x-grid-item .x-item-disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ JSSRC= \
|
||||
form/IPRefSelector.js \
|
||||
form/MDevSelector.js \
|
||||
form/MemoryField.js \
|
||||
form/MultiPCISelector.js \
|
||||
form/NetworkCardSelector.js \
|
||||
form/NodeSelector.js \
|
||||
form/PCISelector.js \
|
||||
|
288
www/manager6/form/MultiPCISelector.js
Normal file
288
www/manager6/form/MultiPCISelector.js
Normal file
@ -0,0 +1,288 @@
|
||||
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();
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue
Block a user