pve-manager/www/manager6/window/PCIMapEdit.js
Dominik Csapak 08526cf56e ui: pci/usb mapping: rework mapping panel for better user experience
by removing the confusing buttons in the toolbar and adding them as
actions in an actioncolumn. There a only relevant actions are visible
and get a more expressive tooltip

with this, we now differentiate between 4 modes of the edit window:
* create a new mapping altogether
  - shows all fields
* edit existing mapping on top level
  - show only 'global' fields (comment, mdev), so no mappings
* add new host mapping
  - shows nodeselector, mapping (and mdev, but disabled)
    (informational only)
* edit existing host mapping
  - show selected node (displayfield) mdev and mappings, but only
    mappings are editable

we have to split the nodeselector into two fields, since the disabling
cbind does not pass through to the editconfig (and thus makes the form
invalid if we try that)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-21 10:03:52 +02:00

280 lines
6.3 KiB
JavaScript

Ext.define('PVE.window.PCIMapEditWindow', {
extend: 'Proxmox.window.Edit',
mixins: ['Proxmox.Mixin.CBind'],
width: 800,
subject: gettext('PCI mapping'),
onlineHelp: 'resource_mapping',
method: 'POST',
cbindData: function(initialConfig) {
let me = this;
me.isCreate = (!me.name || !me.nodename) && !me.entryOnly;
me.method = me.name ? 'PUT' : 'POST';
me.hideMapping = !!me.entryOnly;
me.hideComment = me.name && !me.entryOnly;
me.hideNodeSelector = me.nodename || me.entryOnly;
me.hideNode = !me.nodename || !me.hideNodeSelector;
return {
name: me.name,
nodename: me.nodename,
};
},
submitUrl: function(_url, data) {
let me = this;
let name = me.method === 'PUT' ? me.name : '';
return `/cluster/mapping/pci/${name}`;
},
controller: {
xclass: 'Ext.app.ViewController',
onGetValues: function(values) {
let me = this;
let view = me.getView();
if (view.method === "POST") {
delete me.digest;
}
if (values.iommugroup === -1) {
delete values.iommugroup;
}
let nodename = values.node ?? view.nodename;
delete values.node;
if (me.originalMap) {
let otherMaps = PVE.Parser
.filterPropertyStringList(me.originalMap, (e) => e.node !== nodename);
if (otherMaps.length) {
values.map = values.map.concat(otherMaps);
}
}
return values;
},
onSetValues: function(values) {
let me = this;
let view = me.getView();
me.originalMap = [...values.map];
let configuredNodes = [];
values.map = PVE.Parser.filterPropertyStringList(values.map, (e) => {
configuredNodes.push(e.node);
return e.node === view.nodename;
});
me.lookup('nodeselector').disallowedNodes = configuredNodes;
return values;
},
checkIommu: function(store, records, success) {
let me = this;
if (!success || !records.length) {
return;
}
me.lookup('iommu_warning').setVisible(
records.every((val) => val.data.iommugroup === -1),
);
let value = me.lookup('pciselector').getValue();
me.checkIsolated(value);
},
checkIsolated: function(value) {
let me = this;
let store = me.lookup('pciselector').getStore();
let isIsolated = function(entry) {
let isolated = true;
let parsed = PVE.Parser.parsePropertyString(entry);
parsed.iommugroup = parseInt(parsed.iommugroup, 10);
if (!parsed.iommugroup) {
return isolated;
}
store.each(({ data }) => {
let isSubDevice = data.id.startsWith(parsed.path);
if (data.iommugroup === parsed.iommugroup && data.id !== parsed.path && !isSubDevice) {
isolated = false;
return false;
}
return true;
});
return isolated;
};
let showWarning = false;
if (Ext.isArray(value)) {
for (const entry of value) {
if (!isIsolated(entry)) {
showWarning = true;
break;
}
}
} else {
showWarning = isIsolated(value);
}
me.lookup('group_warning').setVisible(showWarning);
},
mdevChange: function(mdevField, value) {
this.lookup('pciselector').setMdev(value);
},
nodeChange: function(_field, value) {
this.lookup('pciselector').setNodename(value);
},
pciChange: function(_field, value) {
let me = this;
me.lookup('multiple_warning').setVisible(Ext.isArray(value) && value.length > 1);
me.checkIsolated(value);
},
control: {
'field[name=mdev]': {
change: 'mdevChange',
},
'pveNodeSelector': {
change: 'nodeChange',
},
'pveMultiPCISelector': {
change: 'pciChange',
},
},
},
items: [
{
xtype: 'inputpanel',
onGetValues: function(values) {
return this.up('window').getController().onGetValues(values);
},
onSetValues: function(values) {
return this.up('window').getController().onSetValues(values);
},
columnT: [
{
xtype: 'displayfield',
reference: 'iommu_warning',
hidden: true,
columnWidth: 1,
padding: '0 0 10 0',
value: gettext('No IOMMU detected, please activate it. See Documentation for further information.'),
userCls: 'pmx-hint',
},
{
xtype: 'displayfield',
reference: 'multiple_warning',
hidden: true,
columnWidth: 1,
padding: '0 0 10 0',
value: gettext('When multiple devices are selected, the first free one will be chosen on guest start.'),
userCls: 'pmx-hint',
},
{
xtype: 'displayfield',
reference: 'group_warning',
hidden: true,
columnWidth: 1,
padding: '0 0 10 0',
itemId: 'iommuwarning',
value: gettext('A selected device is not in a separate IOMMU group, make sure this is intended.'),
userCls: 'pmx-hint',
},
],
column1: [
{
xtype: 'pmxDisplayEditField',
fieldLabel: gettext('Name'),
labelWidth: 120,
cbind: {
editable: '{!name}',
value: '{name}',
submitValue: '{isCreate}',
},
name: 'id',
allowBlank: false,
},
{
xtype: 'displayfield',
fieldLabel: gettext('Mapping on Node'),
labelWidth: 120,
name: 'node',
cbind: {
value: '{nodename}',
disabled: '{hideNode}',
hidden: '{hideNode}',
},
allowBlank: false,
},
{
xtype: 'pveNodeSelector',
reference: 'nodeselector',
fieldLabel: gettext('Mapping on Node'),
labelWidth: 120,
name: 'node',
cbind: {
disabled: '{hideNodeSelector}',
hidden: '{hideNodeSelector}',
},
allowBlank: false,
},
],
column2: [
{
xtype: 'proxmoxcheckbox',
fieldLabel: gettext('Use with Mediated Devices'),
labelWidth: 200,
reference: 'mdev',
name: 'mdev',
cbind: {
deleteEmpty: '{!isCreate}',
disabled: '{hideComment}',
},
},
],
columnB: [
{
xtype: 'pveMultiPCISelector',
fieldLabel: gettext('Device'),
labelWidth: 120,
height: 300,
reference: 'pciselector',
name: 'map',
cbind: {
nodename: '{nodename}',
disabled: '{hideMapping}',
hidden: '{hideMapping}',
},
allowBlank: false,
onLoadCallBack: 'checkIommu',
margin: '0 0 10 0',
},
{
xtype: 'proxmoxtextfield',
fieldLabel: gettext('Comment'),
labelWidth: 120,
submitValue: true,
name: 'description',
cbind: {
deleteEmpty: '{!isCreate}',
disabled: '{hideComment}',
hidden: '{hideComment}',
},
},
],
},
],
});