[pve-devel] [PATCH manager] ui: declarative LXC Create

Thomas Lamprecht t.lamprecht at proxmox.com
Wed Feb 21 16:45:58 CET 2018


add setNodename method to FileSelector and a setUnprivileged to
MPEdit, this allows to make those properties bindable

Reset MPEdits quota checkbox when it gets disabled

Move the loadSSHKeyFromFile helper to the PVE.Utils singleton

And then, with all those changes transform the LXC Create wizard to
a declarative style, in one go. Maybe it's better to just look at the
end result than the diff...

Signed-off-by: Thomas Lamprecht <t.lamprecht at proxmox.com>
---

I did not see easy intermediate steps that make sense from a git
history POV, so just sending ti as one change...

 www/manager6/Utils.js             |  19 ++
 www/manager6/form/FileSelector.js |   4 +
 www/manager6/lxc/CreateWizard.js  | 597 ++++++++++++++++++--------------------
 www/manager6/lxc/MPEdit.js        |  13 +-
 www/manager6/lxc/Network.js       |  11 +-
 5 files changed, 325 insertions(+), 319 deletions(-)

diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index 8f80c76a..87699e56 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -875,6 +875,25 @@ Ext.define('PVE.Utils', { utilities: {
 
 	    delete values[fieldname];
 	}
+    },
+
+    loadSSHKeyFromFile: function(file, callback) {
+	// ssh-keygen produces 740 bytes for an average 4096 bit rsa key, with
+	// a user at host comment, 1420 for 8192 bits; current max is 16kbit
+	// assume: 740*8 for max. 32kbit (5920 byte file)
+	// round upwards to nearest nice number => 8192 bytes, leaves lots of comment space
+	if (file.size > 8192) {
+	    Ext.Msg.alert(gettext('Error'), gettext("Invalid file size: ") + file.size);
+	    return;
+	}
+	/*global
+	  FileReader
+	*/
+	var reader = new FileReader();
+	reader.onload = function(evt) {
+	    callback(evt.target.result);
+	};
+	reader.readAsText(file);
     }
 },
 
diff --git a/www/manager6/form/FileSelector.js b/www/manager6/form/FileSelector.js
index 9afbf821..3dc50720 100644
--- a/www/manager6/form/FileSelector.js
+++ b/www/manager6/form/FileSelector.js
@@ -47,6 +47,10 @@ Ext.define('PVE.form.FileSelector', {
 	me.store.load();
     },
 
+    setNodename: function(nodename) {
+	this.setStorage(undefined, nodename);
+    },
+
     store: {
 	model: 'pve-storage-content'
     },
diff --git a/www/manager6/lxc/CreateWizard.js b/www/manager6/lxc/CreateWizard.js
index 5b069317..9abe7887 100644
--- a/www/manager6/lxc/CreateWizard.js
+++ b/www/manager6/lxc/CreateWizard.js
@@ -1,342 +1,317 @@
-/*global
-  FileReader
-*/
-
+/*jslint confusion: true*/
 Ext.define('PVE.lxc.CreateWizard', {
     extend: 'PVE.window.Wizard',
+    mixins: ['Proxmox.Mixin.CBind'],
 
-    loadSSHKeyFromFile: function(file) {
-	var me = this;
-	// ssh-keygen produces 740 bytes for an average 4096 bit rsa key, with
-	// a user at host comment, 1420 for 8192 bits; current max is 16kbit
-	// assume: 740*8 for max. 32kbit (5920 byte file)
-	// round upwards to nearest nice number => 8192 bytes, leaves lots of comment space
-	if (file.size > 8192) {
-	    Ext.Msg.alert(gettext('Error'), gettext("Invalid file size: ") + file.size);
-	    return;
+    viewModel: {
+	data: {
+	    nodename: '',
+	    storage: '',
+	    unprivileged: false
 	}
-	var reader = new FileReader();
-	reader.onload = function(evt) {
-	    me.sshkeyfield.setValue(evt.target.result);
-	};
-	reader.readAsText(file);
     },
 
-    initComponent: function() {
-	var me = this;
+    cbindData: {
+	nodename: undefined
+    },
+
+    subject: gettext('LXC Container'),
 
-	var summarystore = Ext.create('Ext.data.Store', {
-	    model: 'KeyValue',
-	    sorters: [
+    items: [
+	{
+	    xtype: 'inputpanel',
+	    title: gettext('General'),
+	    onlineHelp: 'pct_general',
+	    column1: [
+		{
+		    xtype: 'pveNodeSelector',
+		    name: 'nodename',
+		    cbind: {
+			selectCurNode: '{!nodename}',
+			preferredValue: '{nodename}'
+		    },
+		    bind: {
+			value: '{nodename}'
+		    },
+		    fieldLabel: gettext('Node'),
+		    allowBlank: false,
+		    onlineValidator: true
+		},
+		{
+		    xtype: 'pveGuestIDSelector',
+		    name: 'vmid', // backend only knows vmid
+		    guestType: 'lxc',
+		    value: '',
+		    loadNextFreeID: true,
+		    validateExists: false
+		},
+		{
+		    xtype: 'proxmoxtextfield',
+		    name: 'hostname',
+		    vtype: 'DnsName',
+		    value: '',
+		    fieldLabel: gettext('Hostname'),
+		    skipEmptyText: true,
+		    allowBlank: true
+		},
 		{
-		    property : 'key',
-		    direction: 'ASC'
+		    xtype: 'proxmoxcheckbox',
+		    name: 'unprivileged',
+		    value: false,
+		    bind: {
+			value: '{unprivileged}'
+		    },
+		    fieldLabel: gettext('Unprivileged container')
+		}
+	    ],
+	    column2: [
+		{
+		    xtype: 'pvePoolSelector',
+		    fieldLabel: gettext('Resource Pool'),
+		    name: 'pool',
+		    submitValue: false,
+		    value: '',
+		    allowBlank: true
+		},
+		{
+		    xtype: 'textfield',
+		    inputType: 'password',
+		    name: 'password',
+		    value: '',
+		    fieldLabel: gettext('Password'),
+		    allowBlank: false,
+		    minLength: 5,
+		    change: function(f, value) {
+			if (!f.rendered) {
+			    return;
+			}
+			f.uo().down('field[name=confirmpw]').validate();
+		    }
+		},
+		{
+		    xtype: 'textfield',
+		    inputType: 'password',
+		    name: 'confirmpw',
+		    value: '',
+		    fieldLabel: gettext('Confirm password'),
+		    allowBlank: true,
+		    submitValue: false,
+		    validator: function(value) {
+			var pw = this.up().down('field[name=password]').getValue();
+			if (pw !== value) {
+			    return "Passwords do not match!";
+			}
+			return true;
+		    }
+		},
+		{
+		    xtype: 'proxmoxtextfield',
+		    name: 'ssh-public-keys',
+		    value: '',
+		    fieldLabel: gettext('SSH public key'),
+		    allowBlank: true,
+		    validator: function(value) {
+			var pwfield = this.up().down('field[name=password]');
+			if (value.length) {
+			    var key = PVE.Parser.parseSSHKey(value);
+			    if (!key) {
+				return "Failed to recognize ssh key";
+			    }
+			    pwfield.allowBlank = true;
+			} else {
+			    pwfield.allowBlank = false;
+			}
+			pwfield.validate();
+			return true;
+		    },
+		    afterRender: function() {
+			if (!window.FileReader) {
+			    // No FileReader support in this browser
+			    return;
+			}
+			var cancel = function(ev) {
+			    ev = ev.event;
+			    if (ev.preventDefault) {
+				ev.preventDefault();
+			    }
+			};
+			var field = this;
+			field.inputEl.on('dragover', cancel);
+			field.inputEl.on('dragenter', cancel);
+			field.inputEl.on('drop', function(ev) {
+			    ev = ev.event;
+			    if (ev.preventDefault) {
+				ev.preventDefault();
+			    }
+			    var files = ev.dataTransfer.files;
+			    PVE.Utils.loadSSHKeyFromFile(files[0], function(v) {
+				field.setValue(v);
+			    });
+			});
+		    }
+		},
+		{
+		    xtype: 'filebutton',
+		    name: 'file',
+		    hidden: !window.FileReader,
+		    text: gettext('Load SSH Key File'),
+		    listeners: {
+			change: function(btn, e, value) {
+			    e = e.event;
+			    var field = this.up().down('proxmoxtextfield[name=ssh-public-keys]');
+			    PVE.Utils.loadSSHKeyFromFile(e.target.files[0], function(v) {
+				field.setValue(v);
+			    });
+			    btn.reset();
+			}
+		    }
 		}
 	    ]
-	});
-
-	var tmplsel = Ext.create('PVE.form.FileSelector', {
-	    name: 'ostemplate',
-	    storageContent: 'vztmpl',
-	    fieldLabel: gettext('Template'),
-	    allowBlank: false
-	});
-
-	var tmplstoragesel = Ext.create('PVE.form.StorageSelector', {
-	    name: 'tmplstorage',
-	    fieldLabel: gettext('Storage'),
-	    storageContent: 'vztmpl',
-	    autoSelect: true,
-	    allowBlank: false,
-	    listeners: {
-		change: function(f, value) {
-		    tmplsel.setStorage(value);
+	},
+	{
+	    xtype: 'inputpanel',
+	    title: gettext('Template'),
+	    onlineHelp: 'pct_container_images',
+	    column1: [
+		{
+		    xtype: 'pveStorageSelector',
+		    name: 'tmplstorage',
+		    fieldLabel: gettext('Storage'),
+		    storageContent: 'vztmpl',
+		    autoSelect: true,
+		    allowBlank: false,
+		    bind: {
+			value: '{storage}',
+			nodename: '{nodename}'
+		    }
+		},
+		{
+		    xtype: 'pveFileSelector',
+		    name: 'ostemplate',
+		    storageContent: 'vztmpl',
+		    fieldLabel: gettext('Template'),
+		    bind: {
+			storage: '{storage}',
+			nodename: '{nodename}'
+		    },
+		    allowBlank: false
 		}
-	    }
-	});
-
-	var rootfspanel = Ext.create('PVE.lxc.MountPointInputPanel', {
+	    ]
+	},
+	{
+	    xtype: 'pveLxcMountPointInputPanel',
 	    title: gettext('Root Disk'),
 	    insideWizard: true,
 	    isCreate: true,
 	    unused: false,
-	    unprivileged: false,
+	    bind: {
+		nodename: '{nodename}',
+		unprivileged: '{unprivileged}'
+	    },
 	    confid: 'rootfs'
-	});
-
-	var networkpanel = Ext.create('PVE.lxc.NetworkInputPanel', {
+	},
+	{
+	    xtype: 'pveLxcCPUInputPanel',
+	    title: gettext('CPU'),
+	    insideWizard: true
+	},
+	{
+	    xtype: 'pveLxcMemoryInputPanel',
+	    title: gettext('Memory'),
+	    insideWizard: true
+	},
+	{
+	    xtype: 'pveLxcNetworkInputPanel',
 	    title: gettext('Network'),
 	    insideWizard: true,
-	    dataCache: {},
+	    bind: {
+		nodename: '{nodename}'
+	    },
 	    isCreate: true
-	});
-
-	var passwordfield = Ext.createWidget('textfield', {
-	    inputType: 'password',
-	    name: 'password',
-	    value: '',
-	    fieldLabel: gettext('Password'),
-	    allowBlank: false,
-	    minLength: 5,
-	    change: function(f, value) {
-		if (!me.rendered) {
-		    return;
-		}
-		me.down('field[name=confirmpw]').validate();
-	    }
-	});
-
-	/*jslint confusion: true */
-	/* the validator function can return either a string or a boolean */
-	me.sshkeyfield = Ext.createWidget('proxmoxtextfield', {
-	    name: 'ssh-public-keys',
-	    value: '',
-	    fieldLabel: gettext('SSH public key'),
-	    allowBlank: true,
-	    validator: function(value) {
-		if (value.length) {
-		    var key = PVE.Parser.parseSSHKey(value);
-		    if (!key) {
-			return "Failed to recognize ssh key";
-		    }
-		    me.down('field[name=password]').allowBlank = true;
-		} else {
-		    me.down('field[name=password]').allowBlank = false;
-		}
-		me.down('field[name=password]').validate();
-		return true;
-	    },
-	    afterRender: function() {
-		if (!window.FileReader) {
-		    // No FileReader support in this browser
-		    return;
-		}
-		var cancel = function(ev) {
-		    ev = ev.event;
-		    if (ev.preventDefault) {
-			ev.preventDefault();
-		    }
-		};
-		me.sshkeyfield.inputEl.on('dragover', cancel);
-		me.sshkeyfield.inputEl.on('dragenter', cancel);
-		me.sshkeyfield.inputEl.on('drop', function(ev) {
-		    ev = ev.event;
-		    if (ev.preventDefault) {
-			ev.preventDefault();
-		    }
-		    var files = ev.dataTransfer.files;
-		    me.loadSSHKeyFromFile(files[0]);
-		});
-	    }
-	});
-
-	var column2 = [
-	    {
-		xtype: 'pvePoolSelector',
-		fieldLabel: gettext('Resource Pool'),
-		name: 'pool',
-		value: '',
-		allowBlank: true
-	    },
-	    passwordfield,
-	    {
-		xtype: 'textfield',
-		inputType: 'password',
-		name: 'confirmpw',
-		value: '',
-		fieldLabel: gettext('Confirm password'),
-		allowBlank: true,
-		validator: function(value) {
-		    var pw = me.down('field[name=password]').getValue();
-		    if (pw !== value) {
-			return "Passwords do not match!";
-		    }
-		    return true;
-		}
-	    },
-	    me.sshkeyfield
-	];
-	/*jslint confusion: false */
-
-	if (window.FileReader) {
-	    column2.push({
-		xtype: 'filebutton',
-		name: 'file',
-		text: gettext('Load SSH Key File'),
-		listeners: {
-		    change: function(btn, e, value) {
-			e = e.event;
-			me.loadSSHKeyFromFile(e.target.files[0]);
-			btn.reset();
-		    }
-		}
-	    });
-	}
-
-	Ext.applyIf(me, {
-	    subject: gettext('LXC Container'),
+	},
+	{
+	    xtype: 'pveLxcDNSInputPanel',
+	    title: gettext('DNS'),
+	    insideWizard: true
+	},
+	{
+	    title: gettext('Confirm'),
+	    layout: 'fit',
 	    items: [
 		{
-		    xtype: 'inputpanel',
-		    title: gettext('General'),
-		    onlineHelp: 'pct_general',
-		    column1: [
-			{
-			    xtype: 'pveNodeSelector',
-			    name: 'nodename',
-			    selectCurNode: !me.nodename,
-			    preferredValue: me.nodename,
-			    fieldLabel: gettext('Node'),
-			    allowBlank: false,
-			    onlineValidator: true,
-			    listeners: {
-				change: function(f, value) {
-				    tmplstoragesel.setNodename(value);
-				    tmplsel.setStorage(undefined, value);
-				    networkpanel.setNodename(value);
-				    rootfspanel.setNodename(value);
-				}
-			    }
-			},
-			{
-			    xtype: 'pveGuestIDSelector',
-			    name: 'vmid', // backend only knows vmid
-			    guestType: 'lxc',
-			    value: '',
-			    loadNextFreeID: true,
-			    validateExists: false
-			},
-			{
-			    xtype: 'proxmoxtextfield',
-			    name: 'hostname',
-			    vtype: 'DnsName',
-			    value: '',
-			    fieldLabel: gettext('Hostname'),
-			    skipEmptyText: true,
-			    allowBlank: true
-			},
-			{
-			    xtype: 'proxmoxcheckbox',
-			    name: 'unprivileged',
-			    value: '',
-			    listeners: {
-				change: function(f, value) {
-				    if (value) {
-					rootfspanel.down('field[name=quota]').setValue(false);
-				    }
-				    rootfspanel.unprivileged = value;
-				    var hdsel = rootfspanel.down('#hdstorage');
-				    hdsel.fireEvent('change', hdsel, hdsel.getValue());
-				}
-			    },
-			    fieldLabel: gettext('Unprivileged container')
-			}
-		    ],
-		    column2: column2,
-		    onGetValues: function(values) {
-			delete values.confirmpw;
-			if (!values.pool) {
-			    delete values.pool;
-			}
-			return values;
-		    }
-		},
-		{
-		    xtype: 'inputpanel',
-		    title: gettext('Template'),
-		    onlineHelp: 'pct_container_images',
-		    column1: [ tmplstoragesel, tmplsel]
-		},
-		rootfspanel,
-		{
-		    xtype: 'pveLxcCPUInputPanel',
-		    title: gettext('CPU'),
-		    insideWizard: true
-		},
-		{
-		    xtype: 'pveLxcMemoryInputPanel',
-		    title: gettext('Memory'),
-		    insideWizard: true
-		},
-		networkpanel,
-		{
-		    xtype: 'pveLxcDNSInputPanel',
-		    title: gettext('DNS'),
-		    insideWizard: true
-		},
-		{
-		    title: gettext('Confirm'),
-		    layout: 'fit',
-		    items: [
-			{
-			    xtype: 'grid',
-			    store: summarystore,
-			    columns: [
-				{header: 'Key', width: 150, dataIndex: 'key'},
-				{header: 'Value', flex: 1, dataIndex: 'value'}
-			    ]
-			}
-		    ],
-		    listeners: {
-			show: function(panel) {
-			    var form = me.down('form').getForm();
-			    var kv = me.getValues();
-			    var data = [];
-			    Ext.Object.each(kv, function(key, value) {
-				if (key === 'delete' || key === 'tmplstorage') { // ignore
-				    return;
-				}
-				if (key === 'password') { // don't show pw
-				    return;
-				}
-				var html = Ext.htmlEncode(Ext.JSON.encode(value));
-				data.push({ key: key, value: value });
-			    });
-			    summarystore.suspendEvents();
-			    summarystore.removeAll();
-			    summarystore.add(data);
-			    summarystore.sort();
-			    summarystore.resumeEvents();
-			    summarystore.fireEvent('refresh');
-			}
+		    xtype: 'grid',
+		    store: {
+			model: 'KeyValue',
+			sorters: [{
+				property : 'key',
+				direction: 'ASC'
+			}]
 		    },
-		    onSubmit: function() {
-			var kv = me.getValues();
-			delete kv['delete'];
+		    columns: [
+			{header: 'Key', width: 150, dataIndex: 'key'},
+			{header: 'Value', flex: 1, dataIndex: 'value'}
+		    ]
+		}
+	    ],
+	    listeners: {
+		show: function(panel) {
+		    var wizard = this.up('window');
+		    var kv = wizard.getValues();
+		    var data = [];
+		    Ext.Object.each(kv, function(key, value) {
+			if (key === 'delete' || key === 'tmplstorage') { // ignore
+			    return;
+			}
+			if (key === 'password') { // don't show pw
+			    return;
+			}
+			var html = Ext.htmlEncode(Ext.JSON.encode(value));
+			data.push({ key: key, value: value });
+		    });
 
-			var nodename = kv.nodename;
-			delete kv.nodename;
-			delete kv.tmplstorage;
+		    var summarystore = panel.down('grid').getStore();
+		    summarystore.suspendEvents();
+		    summarystore.removeAll();
+		    summarystore.add(data);
+		    summarystore.sort();
+		    summarystore.resumeEvents();
+		    summarystore.fireEvent('refresh');
+		}
+	    },
+	    onSubmit: function() {
+		var wizard = this.up('window');
+		var kv = wizard.getValues();
+		delete kv['delete'];
 
-			if (!kv.password.length && kv['ssh-public-keys']) {
-			    delete kv.password;
-			}
+		var nodename = kv.nodename;
+		delete kv.nodename;
+		delete kv.tmplstorage;
+
+		if (!kv.password.length && kv['ssh-public-keys']) {
+		    delete kv.password;
+		}
 
-			Proxmox.Utils.API2Request({
-			    url: '/nodes/' + nodename + '/lxc',
-			    waitMsgTarget: me,
-			    method: 'POST',
-			    params: kv,
-			    success: function(response, opts){
-				var upid = response.result.data;
+		Proxmox.Utils.API2Request({
+		    url: '/nodes/' + nodename + '/lxc',
+		    waitMsgTarget: wizard,
+		    method: 'POST',
+		    params: kv,
+		    success: function(response, opts){
+			var upid = response.result.data;
 
-				var win = Ext.create('Proxmox.window.TaskViewer', {
-				    upid: upid
-				});
-				win.show();
-				me.close();
-			    },
-			    failure: function(response, opts) {
-				Ext.Msg.alert(gettext('Error'), response.htmlStatus);
-			    }
+			var win = Ext.create('Proxmox.window.TaskViewer', {
+			    upid: upid
 			});
+			win.show();
+			wizard.close();
+		    },
+		    failure: function(response, opts) {
+			Ext.Msg.alert(gettext('Error'), response.htmlStatus);
 		    }
-		}
-	    ]
-	});
-
-	me.callParent();
-    }
+		});
+	    }
+	}
+    ]
 });
 
 
diff --git a/www/manager6/lxc/MPEdit.js b/www/manager6/lxc/MPEdit.js
index 827869e8..3f84ed5f 100644
--- a/www/manager6/lxc/MPEdit.js
+++ b/www/manager6/lxc/MPEdit.js
@@ -12,6 +12,10 @@ Ext.define('PVE.lxc.MountPointInputPanel', {
 
     vmconfig: {}, // used to select unused disks
 
+    setUnprivileged: function(unprivileged) {
+	this.unprivileged = unprivileged;
+    },
+
     onGetValues: function(values) {
 	var me = this;
 
@@ -77,7 +81,6 @@ Ext.define('PVE.lxc.MountPointInputPanel', {
 
 	if (mp.type === 'bind') {
 	    me.quota.setDisabled(true);
-	    me.quota.setValue(false);
 	    me.acl.setDisabled(true);
 	    me.acl.setValue('Default');
 	    me.down('#hdstorage').setDisabled(true);
@@ -222,7 +225,12 @@ Ext.define('PVE.lxc.MountPointInputPanel', {
 	    name: 'quota',
 	    defaultValue: 0,
 	    disabled: me.unprivileged,
-	    fieldLabel: gettext('Enable quota')
+	    fieldLabel: gettext('Enable quota'),
+	    listeners: {
+		disable: function() {
+		    this.reset();
+		}
+	    }
 	});
 
 	me.column2 = [
@@ -276,7 +284,6 @@ Ext.define('PVE.lxc.MountPointInputPanel', {
 		}
 		if (rec.data.type === 'zfs' || rec.data.type === 'zfspool') {
 		    me.quota.setDisabled(true);
-		    me.quota.setValue(false);
 		} else {
 		    me.quota.setDisabled(me.unprivileged);
 		}
diff --git a/www/manager6/lxc/Network.js b/www/manager6/lxc/Network.js
index 1b574239..22d30055 100644
--- a/www/manager6/lxc/Network.js
+++ b/www/manager6/lxc/Network.js
@@ -49,17 +49,18 @@ Ext.define('PVE.lxc.NetworkInputPanel', {
     initComponent : function() {
 	var me = this;
 
-	if (!me.dataCache) {
-	    throw "no dataCache specified";
-	}
-	
 	var cdata = {};
 
 	if (me.insideWizard) {
 	    me.ifname = 'net0';
 	    cdata.name = 'eth0';
+	    me.dataCache = {};
 	}
-	
+
+	if (!me.dataCache) {
+	    throw "no dataCache specified";
+	}
+
 	if (!me.isCreate) {
 	    if (!me.ifname) {
 		throw "no interface name specified";
-- 
2.14.2





More information about the pve-devel mailing list