[pve-devel] [PATCH manager 3/4] add usbselector and usb edit window for qemu

Dominik Csapak d.csapak at proxmox.com
Mon May 29 10:46:33 CEST 2017


the edit window has 3 radiobuttons (spice,device,port)

and a checkbox for usb3 (which gets disabled and checked
if you choose a usb3 device)

also it makes use of the help feature

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
this need my previous patch for pve-docs to get the help section
 www/manager6/Makefile            |   2 +
 www/manager6/form/USBSelector.js | 145 ++++++++++++++++++++++++++
 www/manager6/qemu/USBEdit.js     | 213 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 360 insertions(+)
 create mode 100644 www/manager6/form/USBSelector.js
 create mode 100644 www/manager6/qemu/USBEdit.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 0c266ef7..4bfc5fd0 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -70,6 +70,7 @@ JSSRC= 				                 	\
 	form/GlobalSearchField.js			\
 	form/QemuBiosSelector.js			\
 	form/VMSelector.js			\
+	form/USBSelector.js				\
 	dc/Tasks.js					\
 	dc/Log.js					\
 	panel/StatusPanel.js				\
@@ -158,6 +159,7 @@ JSSRC= 				                 	\
 	qemu/SnapshotTree.js				\
 	qemu/Config.js					\
 	qemu/CreateWizard.js				\
+	qemu/USBEdit.js					\
 	lxc/Summary.js					\
 	lxc/Network.js					\
 	lxc/Resources.js				\
diff --git a/www/manager6/form/USBSelector.js b/www/manager6/form/USBSelector.js
new file mode 100644
index 00000000..bc09b8d6
--- /dev/null
+++ b/www/manager6/form/USBSelector.js
@@ -0,0 +1,145 @@
+Ext.define('PVE.form.USBSelector', {
+    extend: 'PVE.form.ComboGrid',
+    alias: ['widget.pveUSBSelector'],
+    allowBlank: false,
+    autoSelect: false,
+    displayField: 'usbid',
+    valueField: 'usbid',
+    editable: true,
+
+    getUSBValue: function() {
+	var me = this;
+	var rec = me.store.findRecord('usbid', me.value);
+	var val = 'host='+ me.value;
+	if (rec && rec.data.speed === "5000") {
+	    val = 'host=' + me.value + ",usb3=1";
+	}
+	return val;
+    },
+
+    validator: function(value) {
+	var me = this;
+	if (me.type === 'device') {
+	    return (/^[a-f0-9]{4}\:[a-f0-9]{4}$/i).test(value);
+	} else if (me.type === 'port') {
+	    return (/^[0-9]+\-[0-9]+(\.[0-9]+)*$/).test(value);
+	}
+	return false;
+    },
+
+    initComponent: function() {
+	var me = this;
+
+	var nodename = me.pveSelNode.data.node;
+
+	if (!nodename) {
+	    throw "no nodename specified";
+	}
+
+	if (me.type !== 'device' && me.type !== 'port') {
+	    throw "no valid type specified";
+	}
+
+	var store = new Ext.data.Store({
+	    model: 'pve-usb-' + me.type,
+	    proxy: {
+                type: 'pve',
+                url: "/api2/json/nodes/" + nodename + "/scan/usb"
+	    },
+	    filters: [
+		function (item) {
+		    return !!item.data.usbpath && !!item.data.prodid && item.data['class'] != 9;
+		}
+	    ]
+	});
+
+	Ext.apply(me, {
+	    store: store,
+            listConfig: {
+		columns: [
+		    {
+			header: (me.type === 'device')?gettext('Device'):gettext('Port'),
+			sortable: true,
+			dataIndex: 'usbid',
+			width: 80
+		    },
+		    {
+			header: gettext('Manufacturer'),
+			sortable: true,
+			dataIndex: 'manufacturer',
+			width: 100
+		    },
+		    {
+			header: gettext('Product'),
+			sortable: true,
+			dataIndex: 'product',
+			flex: 1
+		    },
+		    {
+			header: gettext('Speed'),
+			width: 70,
+			sortable: true,
+			dataIndex: 'speed',
+			renderer: function(value) {
+			    if (value === "5000") {
+				return "USB 3.0";
+			    } else if (value === "480") {
+				return "USB 2.0";
+			    } else {
+				return "USB 1.x";
+			    }
+			}
+		    }
+		]
+	    }
+	});
+
+        me.callParent();
+
+	store.load();
+    }
+
+}, function() {
+
+    Ext.define('pve-usb-device', {
+	extend: 'Ext.data.Model',
+	fields: [
+	    {
+		name: 'usbid',
+		convert: function(val, data) {
+		    if (val) {
+			return val;
+		    }
+		    return data.get('vendid') + ':' + data.get('prodid');
+		}
+	    },
+	    'speed', 'product', 'manufacturer', 'vendid', 'prodid', 'usbpath',
+	    { name: 'port' , type: 'number' },
+	    { name: 'level' , type: 'number' },
+	    { name: 'class' , type: 'number' },
+	    { name: 'devnum' , type: 'number' },
+	    { name: 'busnum' , type: 'number' }
+	]
+    });
+
+    Ext.define('pve-usb-port', {
+	extend: 'Ext.data.Model',
+	fields: [
+	    {
+		name: 'usbid',
+		convert: function(val,data) {
+		    if (val) {
+			return val;
+		    }
+		    return data.get('busnum') + '-' + data.get('usbpath');
+		}
+	    },
+	    'speed', 'product', 'manufacturer', 'vendid', 'prodid', 'usbpath',
+	    { name: 'port' , type: 'number' },
+	    { name: 'level' , type: 'number' },
+	    { name: 'class' , type: 'number' },
+	    { name: 'devnum' , type: 'number' },
+	    { name: 'busnum' , type: 'number' }
+	]
+    });
+});
diff --git a/www/manager6/qemu/USBEdit.js b/www/manager6/qemu/USBEdit.js
new file mode 100644
index 00000000..ad360506
--- /dev/null
+++ b/www/manager6/qemu/USBEdit.js
@@ -0,0 +1,213 @@
+Ext.define('PVE.qemu.USBInputPanel', {
+    extend: 'PVE.panel.InputPanel',
+
+    autoComplete: false,
+    onlineHelp: 'qm_usb_passthrough',
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	control: {
+	    'field[name=usb]': {
+		change: function(field, newValue, oldValue) {
+		    var hwidfield = this.lookupReference('hwid');
+		    var portfield = this.lookupReference('port');
+		    var usb3field = this.lookupReference('usb3');
+		    if (field.inputValue === 'hostdevice') {
+			hwidfield.setDisabled(!newValue);
+		    } else if(field.inputValue === 'port') {
+			portfield.setDisabled(!newValue);
+		    } else if(field.inputValue === 'spice') {
+			usb3field.setDisabled(newValue);
+		    }
+		}
+	    },
+	    'pveUSBSelector': {
+		change: function(field, newValue, oldValue) {
+		    var usbval = field.getUSBValue();
+		    var usb3field = this.lookupReference('usb3');
+		    var usb3 = /usb3/.test(usbval);
+		    if(usb3 && !usb3field.isDisabled()) {
+			usb3field.savedVal = usb3field.getValue();
+			usb3field.setValue(true);
+			usb3field.setDisabled(true);
+		    } else if(!usb3 && usb3field.isDisabled()){
+			var val = (usb3field.savedVal === undefined)?usb3field.originalValue:usb3field.savedVal;
+			usb3field.setValue(val);
+			usb3field.setDisabled(false);
+		    }
+		}
+	    }
+	}
+    },
+
+    setVMConfig: function(vmconfig) {
+	var me = this;
+	me.vmconfig = vmconfig;
+    },
+
+    onGetValues: function(values) {
+	var me = this;
+	if(!me.confid) {
+	    var i;
+	    for (i = 0; i < 6; i++) {
+		if (!me.vmconfig['usb' +  i.toString()]) {
+		    me.confid = 'usb' + i.toString();
+		    break;
+		}
+	    }
+	}
+	var val = "";
+	var type = me.down('radiofield').getGroupValue();
+	switch (type) {
+	    case 'spice':
+		val = 'spice'; break;
+	    case 'hostdevice':
+	    case 'port':
+		val = me.down('pveUSBSelector[name=' + type + ']').getUSBValue();
+		if (!/usb3/.test(val) && me.down('field[name=usb3]').getValue() === true) {
+		    val += ',usb3=1';
+		}
+		break;
+	    default:
+		throw "invalid type selected";
+	}
+
+	values[me.confid] = val;
+	return values;
+    },
+
+    initComponent: function () {
+	var me = this;
+
+	var items = [
+	    {
+		xtype: 'fieldcontainer',
+		defaultType: 'radiofield',
+		items:[
+		    {
+			name: 'usb',
+			inputValue: 'spice',
+			boxLabel: gettext('Spice Port'),
+			submitValue: false,
+			checked: true
+		    },
+		    {
+			name: 'usb',
+			inputValue: 'hostdevice',
+			boxLabel: gettext('Use USB Vendor/Device ID'),
+			submitValue: false
+		    },
+		    {
+			xtype: 'pveUSBSelector',
+			disabled: true,
+			type: 'device',
+			name: 'hostdevice',
+			pveSelNode: me.pveSelNode,
+			editable: true,
+			reference: 'hwid',
+			allowBlank: false,
+			fieldLabel: 'Choose Device',
+			labelAlign: 'right',
+			submitValue: false
+		    },
+		    {
+			name: 'usb',
+			inputValue: 'port',
+			boxLabel: gettext('Use USB Port'),
+			submitValue: false
+		    },
+		    {
+			xtype: 'pveUSBSelector',
+			disabled: true,
+			name: 'port',
+			pveSelNode: me.pveSelNode,
+			editable: true,
+			type: 'port',
+			reference: 'port',
+			allowBlank: false,
+			fieldLabel: gettext('Choose Port'),
+			labelAlign: 'right',
+			submitValue: false
+		    },
+		    {
+			xtype: 'checkbox',
+			name: 'usb3',
+			submitValue: false,
+			reference: 'usb3',
+			fieldLabel: gettext('Use USB3')
+		    }
+		]
+	    }
+	];
+
+	Ext.apply(me, {
+	    items: items
+	});
+
+	me.callParent();
+    }
+});
+
+Ext.define('PVE.qemu.USBEdit', {
+    extend: 'PVE.window.Edit',
+
+    vmconfig: undefined,
+
+    isAdd: true,
+
+    subject: gettext('USB Device'),
+
+
+    initComponent : function() {
+	var me = this;
+
+	me.isCreate = !me.confid;
+
+	var ipanel = Ext.create('PVE.qemu.USBInputPanel', {
+	    confid: me.confid,
+	    pveSelNode: me.pveSelNode
+	});
+
+	Ext.apply(me, {
+	    items: [ ipanel ]
+	});
+
+	me.callParent();
+
+	me.load({
+	    success: function(response, options) {
+		ipanel.setVMConfig(response.result.data);
+		if (me.confid) {
+		    var data = response.result.data[me.confid].split(',');
+		    var port, hostdevice, usb3 = false;
+		    var type = 'spice';
+		    var i;
+		    for (i = 0; i < data.length; i++) {
+			if (/^(host=)?(0x)?[a-zA-Z0-9]{4}\:(0x)?[a-zA-Z0-9]{4}$/.test(data[i])) {
+			    hostdevice = data[i];
+			    hostdevice = hostdevice.replace('host=', '').replace('0x','');
+			    type = 'hostdevice';
+			} else if (/^(host=)?(\d+)\-(\d+(\.\d+)*)$/.test(data[i])) {
+			    port = data[i];
+			    port = port.replace('host=','');
+			    type = 'port';
+			}
+
+			if (/^usb3=(1|on|true)$/.test(data[i])) {
+			    usb3 = true;
+			}
+		    }
+		    var values = {
+			usb : type,
+			hostdevice: hostdevice,
+			port: port,
+			usb3: usb3
+		    };
+
+		    ipanel.setValues(values);
+		}
+	    }
+	});
+    }
+});
-- 
2.11.0





More information about the pve-devel mailing list