The API only accepts resource mapping IDs in the 'pve-configid' format, but the name field of the PCI, USB and directory mapping windows takes any value. A name like 'gpu@node1' is thus only rejected after submitting, with a generic "invalid configuration ID" error that does not tell which characters are allowed. Use the existing 'ConfigId' vtype, so the field is marked invalid right away and its tooltip lists the allowed characters. Signed-off-by: Michal Fox <me@dualfroz.com> Link: https://lore.proxmox.com/20260929141313.2873638-1-me@dualfroz.com
254 lines
7.9 KiB
JavaScript
254 lines
7.9 KiB
JavaScript
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,
|
|
vtype: 'ConfigId',
|
|
},
|
|
{
|
|
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}',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|