[pve-devel] [RFC v0 pve-manager/cloudinit] cloudinit: draft

Alexandre DERUMIER aderumier at odiso.com
Thu Aug 13 07:36:25 CEST 2015


>>This is only a draft! Please comment! 

1)
I don't like too much the "generate delete" (cloudinit) near "delete remove edit revert" 'network config).

If we select the line "network0" for example, we can choose menu "delete" or "remove", it's confusing.

Maybe add a line "cloudinit" on top of the list, and enable/disable menus could help ?


2) maybe the networkconfig display should match lxc panel display?


3) do we really need to "regenerate" the config ? can't we always apply it if we changing/revert network config?

if we change config offline, we regenerate the config at vm start

if we change config online, we could eject cdrom, regenerate config and reattach cdrom



----- Mail original -----
De: "Wolfgang Bumiller" <w.bumiller at proxmox.com>
À: "pve-devel" <pve-devel at pve.proxmox.com>
Envoyé: Mardi 11 Août 2015 16:09:45
Objet: [pve-devel] [RFC v0 pve-manager/cloudinit] cloudinit: draft

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 | 234 +++++++++++++++++++++++++++ 
11 files changed, 728 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 238c30e..461e22a 100644 
--- a/www/css/ext-pve.css 
+++ b/www/css/ext-pve.css 
@@ -55,6 +55,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; 
@@ -186,6 +187,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..8931fae 
--- /dev/null 
+++ b/www/manager/qemu/IPConfigEdit.js 
@@ -0,0 +1,234 @@ 
+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 = {}; 
+ 
+ if (data['ip'] === '' && data['ip6'] === '' && 
+ data['gw'] === '' && data['gw6'] === '') 
+ { 
+ params['delete'] = [me.confid]; 
+ } else { 
+ params[me.confid] = PVE.Parser.printIPConfig(data); 
+ } 
+ 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 


_______________________________________________ 
pve-devel mailing list 
pve-devel at pve.proxmox.com 
http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel 



More information about the pve-devel mailing list