[pve-devel] [RFC v2 cloudinit pve-manager 1/2] cloudinit: draft

Wolfgang Bumiller w.bumiller at proxmox.com
Wed Aug 19 12:56:37 CEST 2015


This is only a draft! Please comment!

* Added a CloudInit tab with network configuration
* The 'Generate' is renamed to 'Regenerate' when a cloudinit
  image exists (regenerate functionality not yet
  implemented).
* Deletion of the cloudinit image from out of the CloudInit
  tab defaults to using the 'force' flag to avoid leaving
  'unused' disks around.
* Added a PVE.Utils.forEachBus to not duplicate all bus
  names and counts.
---
 www/css/ext-pve.css                  |   6 +
 www/images/Makefile                  |   1 +
 www/images/cloudinit.png             | Bin 0 -> 193 bytes
 www/manager/Makefile                 |   3 +
 www/manager/Parser.js                |  57 +++++++
 www/manager/Utils.js                 |  27 +++-
 www/manager/qemu/CloudInit.js        | 296 +++++++++++++++++++++++++++++++++++
 www/manager/qemu/CloudInitCreator.js |  84 ++++++++++
 www/manager/qemu/Config.js           |   5 +
 www/manager/qemu/HardwareView.js     |  52 ++----
 www/manager/qemu/IPConfigEdit.js     | 233 +++++++++++++++++++++++++++
 11 files changed, 727 insertions(+), 37 deletions(-)
 create mode 100644 www/images/cloudinit.png
 create mode 100644 www/manager/qemu/CloudInit.js
 create mode 100644 www/manager/qemu/CloudInitCreator.js
 create mode 100644 www/manager/qemu/IPConfigEdit.js

diff --git a/www/css/ext-pve.css b/www/css/ext-pve.css
index 39c5e50..3a0fa83 100644
--- a/www/css/ext-pve.css
+++ b/www/css/ext-pve.css
@@ -56,6 +56,7 @@
 .pve-itype-icon-node-running,
 .pve-itype-icon-storage,
 .pve-itype-icon-pool,
+.pve-itype-icon-cloudinit,
 .pve-itype-icon-itype
 {
     background-repeat: no-repeat;
@@ -194,6 +195,11 @@
     background-image:url(../images/virt-viewer.png);
 }
 
+.pve-itype-icon-cloudinit
+{
+    background-image:url(../images/cloudinit.png);
+}
+
 .pve-bar-wrap
 {
 
diff --git a/www/images/Makefile b/www/images/Makefile
index 0a6cf85..5bd27bf 100644
--- a/www/images/Makefile
+++ b/www/images/Makefile
@@ -24,6 +24,7 @@ GNOME_IMAGES = 			\
 	keyboard.png		\
 	cdrom.png		\
 	network.png		\
+	cloudinit.png		\
 	drive-harddisk.png	\
 	network-server.png	\
 	connect_established.png	\
diff --git a/www/images/cloudinit.png b/www/images/cloudinit.png
new file mode 100644
index 0000000000000000000000000000000000000000..3b80f9966a057a199637726c977aa139c5114429
GIT binary patch
literal 193
zcmeAS at N?(olHy`uVBq!ia0vp^0zk~e!3HF=pW8M9DYhhUcNd2LAh=-f^2tCE&H|6f
zVxW#|Aj~)+zj6*xkiEpy*OmP~2R9#wqTA#8V?ZH4PZ!4!jq}NO%Ku-lXX9cJW^PS*
zz?;y}#werw`QpuppC^P)m>9CX-TnWy{n5CDgO60%)LqR)woO)&KD_VuehDB5cmg6N
d+%GdSGt{cYOsoob-2gP1!PC{xWt~$(697 at YJX!z%

literal 0
HcmV?d00001

diff --git a/www/manager/Makefile b/www/manager/Makefile
index 129eca7..eecb64d 100644
--- a/www/manager/Makefile
+++ b/www/manager/Makefile
@@ -121,6 +121,8 @@ JSSRC= 				                 	\
 	qemu/BootOrderEdit.js				\
 	qemu/MemoryEdit.js				\
 	qemu/NetworkEdit.js				\
+	qemu/IPConfigEdit.js				\
+	qemu/CloudInitCreator.js			\
 	qemu/Smbios1Edit.js				\
 	qemu/CDEdit.js					\
 	qemu/HDEdit.js					\
@@ -134,6 +136,7 @@ JSSRC= 				                 	\
 	qemu/StartupEdit.js				\
 	qemu/ScsiHwEdit.js				\
 	qemu/Options.js					\
+	qemu/CloudInit.js				\
 	qemu/Snapshot.js				\
 	qemu/Clone.js					\
 	qemu/SnapshotTree.js				\
diff --git a/www/manager/Parser.js b/www/manager/Parser.js
index 5f15a76..fe2cc41 100644
--- a/www/manager/Parser.js
+++ b/www/manager/Parser.js
@@ -146,6 +146,63 @@ Ext.define('PVE.Parser', { statics: {
 	return drivestr;
     },
 
+    parseIPConfig: function(key, value) {
+	if (!(key && value)) {
+	    return;
+	}
+
+	var res = {};
+
+	var errors = false;
+	Ext.Array.each(value.split(','), function(p) {
+	    if (!p || p.match(/^\s*$/)) {
+		return; // continue
+	    }
+
+	    var match_res;
+	    if ((match_res = p.match(/^ip=(\S+)$/)) !== null) {
+		res.ip = match_res[1];
+	    } else if ((match_res = p.match(/^gw=(\S+)$/)) !== null) {
+		res.gw = match_res[1];
+	    } else if ((match_res = p.match(/^ip6=(\S+)$/)) !== null) {
+		res.ip6 = match_res[1];
+	    } else if ((match_res = p.match(/^gw6=(\S+)$/)) !== null) {
+		res.gw6 = match_res[1];
+	    } else {
+		errors = true;
+		return false; // break
+	    }
+	});
+
+	if (errors) {
+	    return;
+	}
+
+	return res;
+    },
+
+    printIPConfig: function(cfg) {
+	var c = "";
+	var str = "";
+	if (cfg.ip) {
+	    str += "ip=" + cfg.ip;
+	    c = ",";
+	}
+	if (cfg.gw) {
+	    str += c + "gw=" + cfg.gw;
+	    c = ",";
+	}
+	if (cfg.ip6) {
+	    str += c + "ip6=" + cfg.ip6;
+	    c = ",";
+	}
+	if (cfg.gw6) {
+	    str += c + "gw6=" + cfg.gw6;
+	    c = ",";
+	}
+	return str;
+    },
+
     parseOpenVZNetIf: function(value) {
 	if (!value) {
 	    return;
diff --git a/www/manager/Utils.js b/www/manager/Utils.js
index 0e2e8a2..adbff79 100644
--- a/www/manager/Utils.js
+++ b/www/manager/Utils.js
@@ -1083,7 +1083,32 @@ Ext.define('PVE.Utils', { statics: {
 	    }
 	    PVE.Utils.setErrorMask(me, msg);
 	});
-    }
+    },
+
+    bus_counts: { ide: 4, sata: 6, scsi: 16, virtio: 16 },
 
+    forEachBus: function(type, func) {
+	if (Ext.isDefined(type)) {
+	    var count = PVE.Utils.bus_counts[type];
+	    if (!count) {
+		Ext.Msg.alert(gettext('Invalid bus type') + ' (' + type + ')');
+		return;
+	    }
+	    for (var i = 0; i < count; i++) {
+		var cont = func(type, i);
+		if (Ext.isDefined(cont) && cont < 0)
+		    return;
+	    }
+	} else {
+	    Ext.each(Object.keys(PVE.Utils.bus_counts), function(busname) {
+		var count = PVE.Utils.bus_counts[busname];
+		for (var i = 0; i < count; i++) {
+		    var cont = func(busname, i);
+		    if (Ext.isDefined(cont) && !cont)
+			return false;
+		}
+	    });
+	}
+    },
 }});
 
diff --git a/www/manager/qemu/CloudInit.js b/www/manager/qemu/CloudInit.js
new file mode 100644
index 0000000..1a4b880
--- /dev/null
+++ b/www/manager/qemu/CloudInit.js
@@ -0,0 +1,296 @@
+/*jslint confusion: true */
+Ext.define('PVE.qemu.CloudInit', {
+    extend: 'PVE.grid.PendingObjectGrid',
+    alias: ['widget.PVE.qemu.CloudInit'],
+
+    initComponent : function() {
+	var me = this;
+	var i, confid;
+
+	me.pveBusId = undefined;
+
+	var nodename = me.pveSelNode.data.node;
+	if (!nodename) {
+	    throw "no node name specified";
+	}
+
+	var vmid = me.pveSelNode.data.vmid;
+	if (!vmid) {
+	    throw "no VM ID specified";
+	}
+
+	var caps = Ext.state.Manager.get('GuiCap');
+
+	var rows = {};
+
+	for (i = 0; i < 32; i++) {
+	    var confid = "ipconfig" + i;
+	    rows[confid] = {
+	        tdCls: 'pve-itype-icon-network',
+	        editor: caps.vms['VM.Config.Network'] ? 'PVE.qemu.IPConfigEdit' : undefined,
+	        never_delete: caps.vms['VM.Config.Network'] ? false : true,
+	        header: gettext('Network') + ' ' + i,
+	    };
+	    rows["net" + i] = {
+		never_delete: caps.vms['VM.Config.Network'] ? false : true,
+		visible: false
+	    };
+	}
+
+	// we also need to know whether there's a cloudinit image already
+	// available
+	PVE.Utils.forEachBus(undefined, function(type, id) {
+	    rows[type + id] = { visible: false };
+	});
+
+	var reload = function() {
+	    me.rstore.load();
+	};
+
+	var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config';
+
+	var sm = Ext.create('Ext.selection.RowModel', {});
+
+	var run_editor = function() {
+	    var rec = sm.getSelection()[0];
+	    if (!rec) {
+		return;
+	    }
+
+	    var rowdef = rows[rec.data.key];
+	    if (!rowdef.editor) {
+		return;
+	    }
+
+	    var editor = rowdef.editor;
+	    var win;
+
+	    if (Ext.isString(editor)) {
+		win = Ext.create(editor, {
+		    pveSelNode: me.pveSelNode,
+		    confid: rec.data.key,
+		    url: '/api2/extjs/' + baseurl
+		});
+	    } else {
+		var config = Ext.apply({
+		    pveSelNode: me.pveSelNode,
+		    confid: rec.data.key,
+		    url: '/api2/extjs/' + baseurl
+		}, rowdef.editor);
+		win = Ext.createWidget(rowdef.editor.xtype, config);
+		win.load();
+	    }
+
+	    win.show();
+	    win.on('destroy', reload);
+	};
+
+	var add_btn = new PVE.button.Button({
+	    text: gettext('Generate'),
+	    disabled: true,
+	    handler: function() {
+		var win = Ext.create('PVE.qemu.CloudInitCreator', {
+		    url: '/api2/extjs/' + baseurl,
+		    pveSelNode: me.pveSelNode,
+		    pveBusId: me.pveBusId
+		});
+		win.on('destroy', reload);
+		win.show();
+	    }
+        });
+
+	var delete_btn = new PVE.button.Button({
+	    text: gettext('Delete'),
+	    disabled: true,
+	    dangerous: true,
+	    confirmMsg: function() {
+		var msg = gettext('Are you sure you want to remove the CloudInit Image?');
+		if (me.pveBusId.match(/^unused\d+$/)) {
+		    msg += " " + gettext('This will permanently erase all image data.');
+		}
+
+		return msg;
+	    },
+	    handler: function(b, e) {
+		PVE.Utils.API2Request({
+		    url: '/api2/extjs/' + baseurl,
+		    waitMsgTarget: me,
+		    method: 'PUT',
+		    params: {
+			'force': 1,
+			'delete': me.pveBusId
+		    },
+		    callback: function() {
+			reload();
+		    },
+		    failure: function (response, opts) {
+			Ext.Msg.alert('Error', response.htmlStatus);
+		    }
+		});
+	    }
+	});
+
+	var edit_btn = new PVE.button.Button({
+	    text: gettext('Edit'),
+	    selModel: sm,
+	    disabled: true,
+	    handler: run_editor
+        });
+
+	var remove_btn = new PVE.button.Button({
+	    text: gettext('Remove'),
+	    selModel: sm,
+	    disabled: true,
+	    dangerous: true,
+	    confirmMsg: function(rec) {
+		var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
+					    "'" + me.renderKey(rec.data.key, {}, rec) + "'");
+		return msg;
+	    },
+	    handler: function(b, e, rec) {
+		PVE.Utils.API2Request({
+		    url: '/api2/extjs/' + baseurl,
+		    waitMsgTarget: me,
+		    method: 'PUT',
+		    params: {
+			'delete': rec.data.key
+		    },
+		    callback: function() {
+			reload();
+		    },
+		    failure: function (response, opts) {
+			Ext.Msg.alert('Error', response.htmlStatus);
+		    }
+		});
+	    }
+	});
+
+	var revert_btn = new PVE.button.Button({
+	    text: gettext('Revert'),
+	    selModel: sm,
+	    disabled: true,
+	    handler: function(b, e, rec) {
+		var rowdef = me.rows[rec.data.key] || {};
+		var keys = rowdef.multiKey ||  [ rec.data.key ];
+		var revert = keys.join(',');
+		PVE.Utils.API2Request({
+		    url: '/api2/extjs/' + baseurl,
+		    waitMsgTarget: me,
+		    method: 'PUT',
+		    params: {
+			'revert': revert
+		    },
+		    callback: function() {
+			reload();
+		    },
+		    failure: function (response, opts) {
+			Ext.Msg.alert('Error',response.htmlStatus);
+		    }
+		});
+	    }
+	});
+
+	var set_button_status = function() {
+	    var sm = me.getSelectionModel();
+	    var rec = sm.getSelection()[0];
+
+	    if (!rec) {
+		remove_btn.disable();
+		edit_btn.disable();
+		revert_btn.disable();
+		return;
+	    }
+	    var key = rec.data.key;
+	    var value = rec.data.value;
+	    var rowdef = rows[key];
+
+	    var pending = rec.data['delete'] || me.hasPendingChanges(key);
+
+	    remove_btn.setDisabled(rec.data['delete'] || (rowdef.never_delete === true));
+
+	    edit_btn.setDisabled(rec.data['delete'] || !rowdef.editor);
+
+	    revert_btn.setDisabled(!pending);
+	};
+
+	var update_data = function() {
+	    var i;
+	    me.pveBusId = undefined;
+	    PVE.Utils.forEachBus(undefined, function(type, id) {
+		var confid = type + id;
+		var entry = me.rstore.getById(confid);
+		if (!entry)
+		    return; // continue
+		if (entry.data.value.match(/vm-\d+-cloudinit/)) {
+		    me.pveBusId = confid;
+		    return false; // break
+		}
+	    });
+	    if (Ext.isDefined(me.pveBusId)) {
+		add_btn.setText(gettext('Regenerate'));
+		add_btn.enable();
+		delete_btn.enable();
+	    } else {
+		add_btn.setText(gettext('Generate'));
+		add_btn.enable();
+		delete_btn.disable();
+	    }
+
+	    // add/remove arrays because .add/.remove recurses into the
+	    // 'datachange' signal
+	    var to_add = [];
+	    var to_remove = [];
+	    for (i = 0; i < 32; i++) {
+		var cid = "ipconfig" + i;
+		var nid = "net" + i;
+		var dev = me.rstore.getById(nid);
+		var conf = me.rstore.getById(cid);
+		if (!dev) {
+		    if (conf)
+			to_remove.push(conf);
+		    continue;
+		}
+		if (!conf) {
+		    to_add.push({ key: cid, value: '' });
+		    rows[cid].visible = true;
+		} else
+		    rows[conf.data.key].visible = !!dev;
+	    }
+	    if (to_remove.length)
+		me.rstore.remove(to_remove);
+	    if (to_add.length)
+		me.rstore.add(to_add);
+	};
+
+	Ext.applyIf(me, {
+	    url: '/api2/json/' + 'nodes/' + nodename + '/qemu/' + vmid + '/pending',
+	    interval: 5000,
+	    selModel: sm,
+	    cwidth1: 170,
+	    tbar: [ 
+		add_btn,
+		delete_btn,
+		remove_btn,
+		edit_btn,
+		revert_btn
+	    ],
+	    rows: rows,
+	    listeners: {
+		itemdblclick: run_editor,
+		selectionchange: set_button_status
+	    }
+	});
+
+	me.callParent();
+
+	me.on('show', me.rstore.startUpdate);
+	me.on('hide', me.rstore.stopUpdate);
+	me.on('destroy', me.rstore.stopUpdate);	
+
+	me.rstore.on('datachanged', function() {
+	    update_data();
+	    set_button_status();
+	});
+    }
+});
+
diff --git a/www/manager/qemu/CloudInitCreator.js b/www/manager/qemu/CloudInitCreator.js
new file mode 100644
index 0000000..757fca0
--- /dev/null
+++ b/www/manager/qemu/CloudInitCreator.js
@@ -0,0 +1,84 @@
+Ext.define('PVE.qemu.CloudInitCreatePanel', {
+    extend: 'PVE.panel.InputPanel',
+    alias: 'widget.PVE.qemu.CloudInitCreatePanel',
+
+    insideWizard: false,
+
+    vmconfig: {},
+
+    onGetValues: function(values) {
+	var me = this;
+
+	var confid = me.confid || (values.controller + values.deviceid);
+	var params = {};
+
+	params[confid] = values.cdstorage + ":cloudinit";
+
+	return params;
+    },
+
+    setVMConfig: function(vmconfig) {
+	var me = this;
+	me.vmconfig = vmconfig;
+	me.bussel.setVMConfig(vmconfig, true);
+    },
+
+    setNodename: function(nodename) {
+	var me = this;
+	me.cdstoragesel.setNodename(nodename);
+    },
+
+    initComponent : function() {
+	var me = this;
+
+	me.bussel = Ext.createWidget('PVE.form.ControllerSelector', {
+	    noVirtIO: true
+	});
+	me.cdstoragesel = Ext.create('PVE.form.StorageSelector', {
+	    name: 'cdstorage',
+	    nodename: me.nodename,
+	    fieldLabel: gettext('Storage'),
+	    storageContent: 'images',
+	    autoSelect: true,
+	    allowBlank: false,
+	});
+
+	me.column1 = [me.bussel];
+	me.column2 = [me.cdstoragesel];
+
+	me.callParent();
+    }
+});
+
+Ext.define('PVE.qemu.CloudInitCreator', {
+    extend: 'PVE.window.Edit',
+
+    initComponent : function() {
+	/*jslint confusion: true */
+
+	var me = this;
+
+	var nodename = me.pveSelNode.data.node;
+	if (!nodename) { 
+	    throw "no node name specified";	    
+	}
+
+	var ipanel = Ext.create('PVE.qemu.CloudInitCreatePanel', {
+	    nodename: nodename
+	});
+
+	Ext.applyIf(me, {
+	    subject: gettext('Config Drive'),
+	    items: ipanel
+	});
+
+	me.callParent();
+
+	me.load({
+	    success: function(response, options) {
+		me.vmconfig = response.result.data;
+		ipanel.setVMConfig(me.vmconfig);
+	    }
+	});
+    }
+});
diff --git a/www/manager/qemu/Config.js b/www/manager/qemu/Config.js
index 19a331b..005f3e9 100644
--- a/www/manager/qemu/Config.js
+++ b/www/manager/qemu/Config.js
@@ -147,6 +147,11 @@ Ext.define('PVE.qemu.Config', {
 		    xtype: 'PVE.qemu.Options'
 		},
 		{
+		    title: gettext('CloudInit'),
+		    itemId: 'cloudinit',
+		    xtype: 'PVE.qemu.CloudInit'
+		},
+		{
 		    title: gettext('Task History'),
 		    itemId: 'tasks',
 		    xtype: 'pveNodeTasks',
diff --git a/www/manager/qemu/HardwareView.js b/www/manager/qemu/HardwareView.js
index 89f2f4e..d2b437a 100644
--- a/www/manager/qemu/HardwareView.js
+++ b/www/manager/qemu/HardwareView.js
@@ -143,8 +143,8 @@ Ext.define('PVE.qemu.HardwareView', {
 
 	};
 
-	for (i = 0; i < 4; i++) {
-	    confid = "ide" + i;
+	PVE.Utils.forEachBus(undefined, function(type, id) {
+	    confid = type + id;
 	    rows[confid] = {
 		group: 1,
 		tdCls: 'pve-itype-icon-storage',
@@ -153,40 +153,7 @@ Ext.define('PVE.qemu.HardwareView', {
 		header: gettext('Hard Disk') + ' (' + confid +')',
 		cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
 	    };
-	}
-	for (i = 0; i < 6; i++) {
-	    confid = "sata" + i;
-	    rows[confid] = {
-		group: 1,
-		tdCls: 'pve-itype-icon-storage',
-		editor: 'PVE.qemu.HDEdit',
-		never_delete: caps.vms['VM.Config.Disk'] ? false : true,
-		header: gettext('Hard Disk') + ' (' + confid +')',
-		cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
-	    };
-	}
-	for (i = 0; i < 16; i++) {
-	    confid = "scsi" + i;
-	    rows[confid] = {
-		group: 1,
-		tdCls: 'pve-itype-icon-storage',
-		editor: 'PVE.qemu.HDEdit',
-		never_delete: caps.vms['VM.Config.Disk'] ? false : true,
-		header: gettext('Hard Disk') + ' (' + confid +')',
-		cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
-	    };
-	}
-	for (i = 0; i < 16; i++) {
-	    confid = "virtio" + i;
-	    rows[confid] = {
-		group: 1,
-		tdCls: 'pve-itype-icon-storage',
-		editor: 'PVE.qemu.HDEdit',
-		never_delete: caps.vms['VM.Config.Disk'] ? false : true,
-		header: gettext('Hard Disk') + ' (' + confid +')',
-		cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
-	    };
-	}
+	});
 	for (i = 0; i < 32; i++) {
 	    confid = "net" + i;
 	    rows[confid] = {
@@ -504,6 +471,19 @@ Ext.define('PVE.qemu.HardwareView', {
 				    win.on('destroy', reload);
 				    win.show();
 				}
+			    },
+			    {
+				text: gettext('Config Drive'),
+				iconCls: 'pve-itype-icon-cloudinit',
+				disabled: !caps.vms['VM.Config.Network'] || !caps.vms['VM.Config.Disk'],
+				handler: function() {
+				    var win = Ext.create('PVE.qemu.CloudInitCreator', {
+					url: '/api2/extjs/' + baseurl,
+					pveSelNode: me.pveSelNode
+				    });
+				    win.on('destroy', reload);
+				    win.show();
+				}
 			    }
 			]
 		    })
diff --git a/www/manager/qemu/IPConfigEdit.js b/www/manager/qemu/IPConfigEdit.js
new file mode 100644
index 0000000..47b3241
--- /dev/null
+++ b/www/manager/qemu/IPConfigEdit.js
@@ -0,0 +1,233 @@
+Ext.define('PVE.qemu.IPConfigPanel', {
+    extend: 'PVE.panel.InputPanel',
+    alias: 'widget.PVE.qemu.IPConfigPanel',
+
+    insideWizard: false,
+
+    onGetValues: function(values) {
+	var me = this;
+
+	var data = {};
+
+	if (values['ipv4mode'] !== 'static')
+	    data['ip'] = values['ipv4mode'];
+	else {
+	    data['ip'] = values['ip'];
+	}
+
+	if (values['ipv6mode'] !== 'static')
+	    data['ip6'] = values['ipv6mode'];
+	else
+	    data['ip6'] = values['ip6'];
+
+	var params = {};
+
+	var cfg = PVE.Parser.printIPConfig(data);
+	if (cfg === '') {
+	    params['delete'] = [me.confid];
+	} else {
+	    params[me.confid] = cfg;
+	}
+	return params;
+    },
+
+    setIPConfig: function(confid, data) {
+	var me = this;
+
+	me.confid = confid;
+
+	if (data['ip'] === 'dhcp') {
+	    data['ipv4mode'] = data['ip'];
+	    data['ip'] = '';
+	} else {
+	    data['ipv4mode'] = 'static';
+	}
+	if (data['ip6'] === 'dhcp' || data['ip6'] === 'auto') {
+	    data['ipv6mode'] = data['ip6'];
+	    data['ip6'] = '';
+	} else {
+	    data['ipv6mode'] = 'static';
+	}
+
+	me.ipconfig = data;
+	me.setValues(me.ipconfig);
+    },
+
+    initComponent : function() {
+	var me = this;
+
+	me.ipconfig = {};
+	me.confid = 'ipconfig0';
+
+	me.column1 = [
+	    {
+		layout: {
+		    type: 'hbox',
+		    align: 'middle'
+		},
+		border: false,
+		margin: '0 0 5 0',
+		height: 22, // hack: set same height as text fields
+		items: [
+		    {
+			xtype: 'label',
+			text: gettext('IPv4') + ':',
+		    },
+		    {
+			xtype: 'radiofield',
+			boxLabel: gettext('Static'),
+			name: 'ipv4mode',
+			inputValue: 'static',
+			checked: false,
+			margin: '0 0 0 10',
+			listeners: {
+			    change: function(cb, value) {
+				me.down('field[name=ip]').setDisabled(!value);
+				me.down('field[name=gw]').setDisabled(!value);
+			    }
+			}
+		    },
+		    {
+			xtype: 'radiofield',
+			boxLabel: gettext('DHCP'),
+			name: 'ipv4mode',
+			inputValue: 'dhcp',
+			checked: false,
+			margin: '0 0 0 10'
+		    }
+		]
+	    },
+	    {
+		xtype: 'textfield',
+		name: 'ip',
+		vtype: 'IPCIDRAddress',
+		value: '',
+		disabled: true,
+		fieldLabel: gettext('IPv4/CIDR')
+	    },
+	    {
+		xtype: 'textfield',
+		name: 'gw',
+		value: '',
+		vtype: 'IPAddress',
+		disabled: true,
+		fieldLabel: gettext('Gateway') + ' (' + gettext('IPv4') +')',
+		margin: '0 0 3 0' // override bottom margin to account for the menuseparator
+	    },
+	];
+
+	me.column2 = [
+	    {
+		layout: {
+		    type: 'hbox',
+		    align: 'middle'
+		},
+		border: false,
+		margin: '0 0 5 0',
+		height: 22, // hack: set same height as text fields
+		items: [
+		    {
+			xtype: 'label',
+			text: gettext('IPv6') + ':',
+		    },
+		    {
+			xtype: 'radiofield',
+			boxLabel: gettext('Static'),
+			name: 'ipv6mode',
+			inputValue: 'static',
+			checked: false,
+			margin: '0 0 0 10',
+			listeners: {
+			    change: function(cb, value) {
+				me.down('field[name=ip6]').setDisabled(!value);
+				me.down('field[name=gw6]').setDisabled(!value);
+			    }
+			}
+		    },
+		    {
+			xtype: 'radiofield',
+			boxLabel: gettext('DHCP'),
+			name: 'ipv6mode',
+			inputValue: 'dhcp',
+			checked: false,
+			margin: '0 0 0 10'
+		    },
+		    {
+			xtype: 'radiofield',
+			boxLabel: gettext('SLAAC'),
+			name: 'ipv6mode',
+			inputValue: 'auto',
+			checked: false,
+			margin: '0 0 0 10'
+		    }
+		]
+	    },
+	    {
+		xtype: 'textfield',
+		name: 'ip6',
+		value: '',
+		vtype: 'IP6CIDRAddress',
+		disabled: true,
+		fieldLabel: gettext('IPv6/CIDR')
+	    },
+	    {
+		xtype: 'textfield',
+		name: 'gw6',
+		vtype: 'IP6Address',
+		value: '',
+		disabled: true,
+		fieldLabel: gettext('Gateway') + ' (' + gettext('IPv6') +')'
+	    }
+	];
+
+	me.callParent();
+    }
+});
+
+Ext.define('PVE.qemu.IPConfigEdit', {
+    extend: 'PVE.window.Edit',
+
+    isAdd: true,
+
+    initComponent : function() {
+	/*jslint confusion: true */
+
+	var me = this;
+
+	var nodename = me.pveSelNode.data.node;
+	if (!nodename) { 
+	    throw "no node name specified";	    
+	}
+
+	me.create = me.confid ? false : true;
+
+	var ipanel = Ext.create('PVE.qemu.IPConfigPanel', {
+	    confid: me.confid,
+	    nodename: nodename
+	});
+
+	Ext.applyIf(me, {
+	    subject: gettext('Network Config'),
+	    items: ipanel
+	});
+
+	me.callParent();
+
+	me.load({
+	    success: function(response, options) {
+		me.vmconfig = response.result.data;
+		var ipconfig = {};
+		var value = me.vmconfig[me.confid];
+		if (value) {
+		    ipconfig = PVE.Parser.parseIPConfig(me.confid, value);
+		    if (!ipconfig) {
+			Ext.Msg.alert(gettext('Error'), gettext('Unable to parse network configuration'));
+			me.close();
+			return;
+		    }
+		}
+		ipanel.setIPConfig(me.confid, ipconfig);
+	    }
+	});
+    }
+});
-- 
2.1.4





More information about the pve-devel mailing list