Upstream proxmox-git/pve-manager state of the touched files before 55c76c2746

This commit is contained in:
upstream committed 2026-09-29 16:13:13 +02:00
1 parent 11c2368e04
commit 8814724567
3 files changed
+765

No files matched your search

+208
View File
@@ -0,0 +1,208 @@
Ext.define('PVE.window.DirMapEditWindow', {
extend: 'Proxmox.window.Edit',
mixins: ['Proxmox.Mixin.CBind'],
cbindData: function (initialConfig) {
let me = this;
me.isCreate = !me.name;
me.method = me.isCreate ? 'POST' : 'PUT';
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.isCreate ? '' : me.name;
return `/cluster/mapping/dir/${name}`;
},
title: gettext('Add Directory Mapping'),
onlineHelp: 'resource_mapping',
method: 'POST',
controller: {
xclass: 'Ext.app.ViewController',
onGetValues: function (values) {
let me = this;
let view = me.getView();
values.node ??= view.nodename;
let name = values.name;
let description = values.description;
let deletes = values.delete;
delete values.description;
delete values.name;
delete values.delete;
let map = [];
if (me.originalMap) {
map = PVE.Parser.filterPropertyStringList(
me.originalMap,
(e) => e.node !== values.node,
);
}
if (values.path) {
// TODO: Remove this when property string supports quotation of properties
if (!/^\/[^;,=()]+/.test(values.path)) {
let errMsg =
'Value does not look like a valid absolute path.' +
' These symbols are currently not allowed in path: ;,=()\n';
Ext.Msg.alert(gettext('Error'), errMsg);
// prevent sending a broken property string to the API
throw errMsg;
}
map.push(PVE.Parser.printPropertyString(values));
}
values = { map };
if (description) {
values.description = description;
}
if (deletes && !view.isCreate) {
values.delete = deletes;
}
if (view.isCreate) {
values.id = name;
}
return values;
},
onSetValues: function (values) {
let me = this;
let view = me.getView();
me.originalMap = [...values.map];
let configuredNodes = [];
PVE.Parser.filterPropertyStringList(values.map, (e) => {
configuredNodes.push(e.node);
if (e.node === view.nodename) {
values = e;
}
return false;
});
me.lookup('nodeselector').disallowedNodes = configuredNodes;
return values;
},
init: function (view) {
let _me = this;
if (!view.nodename) {
//throw "no nodename given";
}
},
},
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: 'directory-hint',
columnWidth: 1,
value: 'Make sure the directory exists.',
cbind: {
disabled: '{hideMapping}',
hidden: '{hideMapping}',
},
userCls: 'pmx-hint',
},
],
column1: [
{
xtype: 'pmxDisplayEditField',
fieldLabel: gettext('Name'),
cbind: {
editable: '{!name}',
value: '{name}',
submitValue: '{isCreate}',
},
name: 'name',
allowBlank: false,
},
{
xtype: 'pveNodeSelector',
reference: 'nodeselector',
fieldLabel: gettext('Node'),
name: 'node',
cbind: {
disabled: '{hideNodeSelector}',
hidden: '{hideNodeSelector}',
},
allowBlank: false,
},
],
column2: [
{
xtype: 'fieldcontainer',
defaultType: 'radiofield',
layout: 'fit',
cbind: {
disabled: '{hideMapping}',
hidden: '{hideMapping}',
},
items: [
{
xtype: 'textfield',
name: 'path',
reference: 'path',
value: '',
emptyText: gettext('/some/path'),
cbind: {
nodename: '{nodename}',
disabled: '{hideMapping}',
},
allowBlank: false,
fieldLabel: gettext('Path'),
},
],
},
],
columnB: [
{
xtype: 'fieldcontainer',
defaultType: 'radiofield',
layout: 'fit',
cbind: {
disabled: '{hideComment}',
hidden: '{hideComment}',
},
items: [
{
xtype: 'proxmoxtextfield',
fieldLabel: gettext('Comment'),
submitValue: true,
name: 'description',
deleteEmpty: true,
},
],
},
],
},
],
});
+305
View File
@@ -0,0 +1,305 @@
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.globalEdit = !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) {
if (!field.isDisabled()) {
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: '{!globalEdit}',
},
},
{
xtype: 'proxmoxcheckbox',
fieldLabel: gettext('Live Migration Capable'),
labelWidth: 200,
boxLabel: `<i class="fa fa-exclamation-triangle warning"></i> ${gettext('Experimental')}`,
reference: 'live-migration-capable',
name: 'live-migration-capable',
cbind: {
deleteEmpty: '{!isCreate}',
disabled: '{!globalEdit}',
},
},
],
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: '{!globalEdit}',
hidden: '{!globalEdit}',
},
},
],
},
],
});
+252
View File
@@ -0,0 +1,252 @@
Ext.define('PVE.window.USBMapEditWindow', {
extend: 'Proxmox.window.Edit',
mixins: ['Proxmox.Mixin.CBind'],
cbindData: function (initialConfig) {
let me = this;
me.isCreate = !me.name;
me.method = me.isCreate ? 'POST' : 'PUT';
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.isCreate ? '' : me.name;
return `/cluster/mapping/usb/${name}`;
},
title: gettext('Add USB mapping'),
onlineHelp: 'resource_mapping',
method: 'POST',
controller: {
xclass: 'Ext.app.ViewController',
onGetValues: function (values) {
let me = this;
let view = me.getView();
values.node ??= view.nodename;
let type = me.getView().down('radiofield').getGroupValue();
let name = values.name;
let description = values.description;
delete values.description;
delete values.name;
if (type === 'path') {
let usbsel = me.lookup(type);
let usbDev = usbsel
.getStore()
.findRecord('usbid', values[type], 0, false, true, true);
if (!usbDev) {
return {};
}
values.id = `${usbDev.data.vendid}:${usbDev.data.prodid}`;
}
let map = [];
if (me.originalMap) {
map = PVE.Parser.filterPropertyStringList(
me.originalMap,
(e) => e.node !== values.node,
);
}
if (values.id) {
map.push(PVE.Parser.printPropertyString(values));
}
values = { map };
if (description) {
values.description = description;
}
if (view.isCreate) {
values.id = name;
}
return values;
},
onSetValues: function (values) {
let me = this;
let view = me.getView();
me.originalMap = [...values.map];
let configuredNodes = [];
PVE.Parser.filterPropertyStringList(values.map, (e) => {
configuredNodes.push(e.node);
if (e.node === view.nodename) {
values = e;
}
return false;
});
me.lookup('nodeselector').disallowedNodes = configuredNodes;
if (values.path) {
values.usb = 'path';
}
return values;
},
modeChange: function (field, value) {
let me = this;
let type = field.inputValue;
let usbsel = me.lookup(type);
usbsel.setDisabled(!value);
},
nodeChange: function (field, value) {
if (!field.isDisabled()) {
this.lookup('id').setNodename(value);
this.lookup('path').setNodename(value);
}
},
init: function (view) {
let _me = this;
if (!view.nodename) {
//throw "no nodename given";
}
},
control: {
radiofield: {
change: 'modeChange',
},
pveNodeSelector: {
change: 'nodeChange',
},
},
},
items: [
{
xtype: 'inputpanel',
onGetValues: function (values) {
return this.up('window').getController().onGetValues(values);
},
onSetValues: function (values) {
return this.up('window').getController().onSetValues(values);
},
column1: [
{
xtype: 'pmxDisplayEditField',
fieldLabel: gettext('Name'),
cbind: {
editable: '{!name}',
value: '{name}',
submitValue: '{isCreate}',
},
name: 'name',
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: 'fieldcontainer',
defaultType: 'radiofield',
layout: 'fit',
cbind: {
disabled: '{hideMapping}',
hidden: '{hideMapping}',
},
items: [
{
name: 'usb',
inputValue: 'id',
checked: true,
boxLabel: gettext('Use USB Vendor/Device ID'),
submitValue: false,
},
{
xtype: 'pveUSBSelector',
type: 'device',
reference: 'id',
name: 'id',
cbind: {
nodename: '{nodename}',
disabled: '{hideMapping}',
},
editable: true,
allowBlank: false,
fieldLabel: gettext('Choose Device'),
labelAlign: 'right',
},
{
name: 'usb',
inputValue: 'path',
boxLabel: gettext('Use USB Port'),
submitValue: false,
},
{
xtype: 'pveUSBSelector',
disabled: true,
name: 'path',
reference: 'path',
cbind: {
nodename: '{nodename}',
},
editable: true,
type: 'port',
allowBlank: false,
fieldLabel: gettext('Choose Port'),
labelAlign: 'right',
},
],
},
],
columnB: [
{
xtype: 'proxmoxtextfield',
fieldLabel: gettext('Comment'),
submitValue: true,
name: 'description',
cbind: {
disabled: '{hideComment}',
hidden: '{hideComment}',
},
},
],
},
],
});