[pve-devel] [RFC manager 8/9] gui: add basic custom CPU model editor

Stefan Reiter s.reiter at proxmox.com
Thu Oct 28 13:41:49 CEST 2021


Supports viewing, deleting, adding and editing existing custom CPU
models. All properties except CPU flags are supported in the editor.

A new control for selecting between different Phys-Bits (default, host,
custom number) is added.

This also seems to be the first use of a non-fa icon in the sidebar, so
add a new class that correctly aligns pve-itype-icon-* elements.

Signed-off-by: Stefan Reiter <s.reiter at proxmox.com>
---
 www/css/ext6-pve.css                  |   4 +
 www/manager6/Makefile                 |   3 +
 www/manager6/dc/CPUTypeEdit.js        |  81 ++++++++++++++
 www/manager6/dc/CPUTypeView.js        | 148 ++++++++++++++++++++++++++
 www/manager6/dc/Config.js             |   6 ++
 www/manager6/form/PhysBitsSelector.js | 128 ++++++++++++++++++++++
 6 files changed, 370 insertions(+)
 create mode 100644 www/manager6/dc/CPUTypeEdit.js
 create mode 100644 www/manager6/dc/CPUTypeView.js
 create mode 100644 www/manager6/form/PhysBitsSelector.js

diff --git a/www/css/ext6-pve.css b/www/css/ext6-pve.css
index e5d792f8..bb9bd8c0 100644
--- a/www/css/ext6-pve.css
+++ b/www/css/ext6-pve.css
@@ -447,6 +447,10 @@
     padding: 0;
 }
 
+.pve-icon-sidebar {
+    margin-top: 5px;
+}
+
 /* displayfield minheight is wrong */
 .x-form-display-field-default {
     min-height: 20px;
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index e5e85aed..abca0a05 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -48,6 +48,7 @@ JSSRC= 							\
 	form/NodeSelector.js				\
 	form/PCISelector.js				\
 	form/PermPathSelector.js			\
+	form/PhysBitsSelector.js			\
 	form/PoolSelector.js				\
 	form/PreallocationSelector.js			\
 	form/PrivilegesSelector.js			\
@@ -131,6 +132,8 @@ JSSRC= 							\
 	dc/ClusterEdit.js				\
 	dc/Config.js					\
 	dc/CorosyncLinkEdit.js				\
+	dc/CPUTypeView.js				\
+	dc/CPUTypeEdit.js				\
 	dc/GroupEdit.js					\
 	dc/GroupView.js					\
 	dc/Guests.js					\
diff --git a/www/manager6/dc/CPUTypeEdit.js b/www/manager6/dc/CPUTypeEdit.js
new file mode 100644
index 00000000..3ad527c4
--- /dev/null
+++ b/www/manager6/dc/CPUTypeEdit.js
@@ -0,0 +1,81 @@
+Ext.define('PVE.dc.CPUTypeEdit', {
+    extend: 'Proxmox.window.Edit',
+    alias: ['widget.pveCpuTypeEdit'],
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    subject: gettext('CPU Type'),
+
+    cbindData: {
+	cputype: '',
+	isCreate: (cfg) => !cfg.cputype,
+    },
+
+    cbind: {
+	autoLoad: get => !get('isCreate'),
+	url: get => `/api2/extjs/nodes/localhost/capabilities/qemu/cpu/model/${get('cputype')}`,
+	method: get => get('isCreate') ? 'POST' : 'PUT',
+	isCreate: get => get('isCreate'),
+    },
+
+    getValues: function() {
+	let me = this;
+	let values = me.callParent();
+
+	PVE.Utils.delete_if_default(values, 'reported-model', '', me.isCreate);
+	PVE.Utils.delete_if_default(values, 'hv-vendor-id', '', me.isCreate);
+	PVE.Utils.delete_if_default(values, 'phys-bits', '', me.isCreate);
+	PVE.Utils.delete_if_default(values, 'flags', '', me.isCreate);
+
+	if (me.isCreate && !values.cputype.match(/^custom-/)) {
+	    values.cputype = 'custom-' + values.cputype;
+	}
+
+	return values;
+    },
+
+    items: [
+	{
+	    xtype: 'inputpanel',
+	    column1: [
+		{
+		    xtype: 'pmxDisplayEditField',
+		    fieldLabel: gettext('Name'),
+		    cbind: {
+			editable: '{isCreate}',
+			value: '{cputype}',
+		    },
+		    name: 'cputype',
+		    allowBlank: false,
+		},
+		{
+		    xtype: 'CPUModelSelector',
+		    fieldLabel: gettext('Reported Model'),
+		    allowCustom: false,
+		    name: 'reported-model',
+		},
+		{
+		    xtype: 'textfield',
+		    fieldLabel: gettext('Hyper-V Vendor'),
+		    name: 'hv-vendor-id',
+		    allowBlank: true,
+		    emptyText: gettext('None'),
+		    maxLength: 12,
+		},
+	    ],
+	    column2: [
+		{
+		    xtype: 'checkbox',
+		    fieldLabel: gettext('Hidden'),
+		    name: 'hidden',
+		    inputValue: 1,
+		    uncheckedValue: 0,
+		},
+		{
+		    xtype: 'PhysBitsSelector',
+		    fieldLabel: gettext('Phys-Bits'),
+		    name: 'phys-bits',
+		},
+	    ],
+	},
+    ],
+});
diff --git a/www/manager6/dc/CPUTypeView.js b/www/manager6/dc/CPUTypeView.js
new file mode 100644
index 00000000..0d560369
--- /dev/null
+++ b/www/manager6/dc/CPUTypeView.js
@@ -0,0 +1,148 @@
+Ext.define('PVE.dc.CPUTypeView', {
+    extend: 'Ext.grid.GridPanel',
+    alias: ['widget.pveCPUTypeView'],
+
+    onlineHelp: 'qm_cpu',
+
+    store: {
+	model: 'pve-custom-cpu-type',
+	proxy: {
+	    type: 'proxmox',
+	    url: "/api2/json/nodes/localhost/capabilities/qemu/cpu/model",
+	    root: 'data.ids',
+	},
+	autoLoad: true,
+	sorters: ['cputype'],
+    },
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	getSelection: function() {
+	    let me = this;
+	    let grid = me.getView();
+	    let selection = grid.getSelection();
+	    if (selection.length === 1) {
+		return selection[0].data;
+	    }
+	    return null;
+	},
+
+	showEditor: function(cputype) {
+	    let me = this;
+	    let param = {};
+	    if (cputype) {
+		Ext.apply(param, { cputype: cputype });
+	    }
+	    let win = Ext.create('PVE.dc.CPUTypeEdit', param);
+	    win.on('destroy', () => me.reload());
+	    win.show();
+	},
+
+	onAdd: function() {
+	    let me = this;
+	    me.showEditor();
+	},
+
+	onEdit: function() {
+	    let me = this;
+	    let selection = me.getSelection();
+	    me.showEditor(selection.cputype);
+	},
+
+	reload: function() {
+	    let me = this;
+	    let grid = me.getView();
+	    let store = grid.store;
+	    store.reload();
+	},
+    },
+
+    columns: [
+	{
+	    header: 'Name',
+	    flex: 1,
+	    sortable: true,
+	    dataIndex: 'cputype',
+	    renderer: val => val.replace(/^custom-/, ''),
+	},
+	{
+	    header: 'Reported Model',
+	    width: '80px',
+	    sortable: true,
+	    dataIndex: 'reported-model',
+	},
+	{
+	    header: 'Phys-Bits',
+	    width: '40px',
+	    sortable: true,
+	    dataIndex: 'phys-bits',
+	},
+	{
+	    header: 'Hidden',
+	    width: '40px',
+	    sortable: true,
+	    dataIndex: 'hidden',
+	},
+	{
+	    header: 'HyperV-Vendor',
+	    width: '80px',
+	    sortable: true,
+	    dataIndex: 'hv-vendor-id',
+	},
+	{
+	    header: 'Flags',
+	    flex: 2,
+	    sortable: true,
+	    dataIndex: 'flags',
+	},
+    ],
+
+    tbar: [
+	{
+	    text: gettext('Add'),
+	    handler: 'onAdd',
+	},
+	'-',
+	{
+	    xtype: 'proxmoxStdRemoveButton',
+	    baseurl: '/api2/extjs/nodes/localhost/capabilities/qemu/cpu/model/',
+	    getRecordName: (rec) => rec.data.cputype,
+	    getUrl: function(rec) {
+		let me = this;
+		return me.baseurl + rec.data.cputype;
+	    },
+	    callback: 'reload',
+	},
+	{
+	    text: gettext('Edit'),
+	    handler: 'onEdit',
+	},
+    ],
+
+    selModel: {
+	xtype: 'Ext.selection.RowModel',
+    },
+
+    listeners: {
+	itemdblclick: function(_, rec) {
+	    let me = this;
+	    me.getController().showEditor(rec.data.cputype);
+	},
+    },
+
+    initComponent: function() {
+	let me = this;
+	me.callParent();
+	Proxmox.Utils.monStoreErrors(me, me.store);
+    },
+
+}, function() {
+    Ext.define('pve-custom-cpu-type', {
+	extend: 'Ext.data.Model',
+	fields: [
+	    'cputype', 'reported-model', 'hv-vendor-id', 'flags', 'phys-bits',
+	    { name: 'hidden', type: 'boolean' },
+	],
+    });
+});
diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js
index 934952d9..cbaabb9e 100644
--- a/www/manager6/dc/Config.js
+++ b/www/manager6/dc/Config.js
@@ -70,6 +70,12 @@ Ext.define('PVE.dc.Config', {
 		title: gettext('Replication'),
 		itemId: 'replication',
 	    },
+	    {
+		xtype: 'pveCPUTypeView',
+		iconCls: 'pve-itype-icon-processor pve-icon pve-icon-sidebar',
+		title: gettext('CPU Types'),
+		itemId: 'cputypes',
+	    },
 	    {
 		xtype: 'pveACLView',
 		title: gettext('Permissions'),
diff --git a/www/manager6/form/PhysBitsSelector.js b/www/manager6/form/PhysBitsSelector.js
new file mode 100644
index 00000000..a18b675f
--- /dev/null
+++ b/www/manager6/form/PhysBitsSelector.js
@@ -0,0 +1,128 @@
+Ext.define('PVE.form.PhysBitsSelector', {
+    extend: 'Ext.form.FieldContainer',
+    alias: 'widget.PhysBitsSelector',
+    mixins: ['Ext.form.field.Field'],
+
+    layout: 'vbox',
+    initialValue: '',
+    originalValue: '',
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	updateNumberField: function() {
+	    let me = this;
+	    let modeCustom = me.lookupReference('modeCustom');
+	    let customNum = me.lookupReference('customNum');
+
+	    customNum.setDisabled(!modeCustom.getValue());
+	    me.getView().validate();
+	},
+
+	listen: {
+	    component: {
+		'*': {
+		    change: function() {
+			let me = this;
+			me.getView().checkChange();
+		    },
+		},
+	    },
+	},
+    },
+
+    getValue: function() {
+	let me = this;
+	let ctrl = me.getController();
+	if (ctrl.lookupReference('modeDefault').getValue()) {
+	    return '';
+	} else if (ctrl.lookupReference('modeHost').getValue()) {
+	    return 'host';
+	} else if (ctrl.lookupReference('modeCustom').getValue()) {
+	    return ctrl.lookupReference('customNum').getValue();
+	}
+	return ''; // shouldn't happen
+    },
+
+    setValue: function(value) {
+	let me = this;
+	let ctrl = me.getController();
+	let modeField;
+
+	if (!value) {
+	    modeField = ctrl.lookupReference('modeDefault');
+	} else if (value === 'host') {
+	    modeField = ctrl.lookupReference('modeHost');
+	} else {
+	    let customNum = me.lookupReference('customNum');
+	    customNum.setValue(value);
+	    modeField = ctrl.lookupReference('modeCustom');
+	}
+
+	modeField.setValue(true);
+	me.checkChange();
+
+	return value;
+    },
+
+    getErrors: function() {
+	let me = this;
+	let ctrl = me.getController();
+	if (ctrl.lookupReference('modeCustom').getValue()) {
+	    return ctrl.lookupReference('customNum').getErrors();
+	}
+	return [];
+    },
+
+    isValid: function() {
+	let me = this;
+	let ctrl = me.getController();
+	return ctrl.lookupReference('customNum').isValid();
+    },
+
+    items: [
+	{
+	    xtype: 'radiofield',
+	    boxLabel: gettext('Default'),
+	    inputValue: 'default',
+	    checked: true,
+	    reference: 'modeDefault',
+	    isFormField: false,
+	},
+	{
+	    xtype: 'radiofield',
+	    boxLabel: gettext('Host'),
+	    inputValue: 'host',
+	    reference: 'modeHost',
+	    isFormField: false,
+	},
+	{
+	    xtype: 'fieldcontainer',
+	    layout: 'hbox',
+	    items: [
+		{
+		    xtype: 'radiofield',
+		    boxLabel: gettext('Custom'),
+		    inputValue: 'custom',
+		    listeners: {
+			change: 'updateNumberField',
+		    },
+		    reference: 'modeCustom',
+		    isFormField: false,
+		},
+		{
+		    xtype: 'numberfield',
+		    width: '60px',
+		    margin: '0 0 0 10px',
+		    minValue: 8,
+		    maxValue: 64,
+		    reference: 'customNum',
+		    allowBlank: false,
+		    isFormField: false,
+		    disabled: true,
+		},
+	    ],
+	},
+    ],
+});
+
-- 
2.30.2





More information about the pve-devel mailing list