[pve-devel] [PATCH v2 manager 2/4] lxc: create/edit/remove mountpoints

Wolfgang Bumiller w.bumiller at proxmox.com
Tue Feb 23 15:00:20 CET 2016


---
 www/manager/lxc/ResourceEdit.js | 338 ++++++++++++++++++++++++++++++++++++++++
 www/manager/lxc/Resources.js    |  77 ++++++++-
 2 files changed, 413 insertions(+), 2 deletions(-)

diff --git a/www/manager/lxc/ResourceEdit.js b/www/manager/lxc/ResourceEdit.js
index 3bff060..327c065 100644
--- a/www/manager/lxc/ResourceEdit.js
+++ b/www/manager/lxc/ResourceEdit.js
@@ -35,6 +35,65 @@ Ext.define('PVE.lxc.CPUEdit', {
     }
 });
 
+Ext.define('PVE.lxc.MountPointEdit', {
+    extend: 'PVE.window.Edit',
+
+    initComponent : function() {
+	var me = this;
+
+	var nodename = me.pveSelNode.data.node;
+	if (!nodename) {
+	    throw "no node name specified";
+	}
+
+	var unused = me.confid && me.confid.match(/^unused\d+$/);
+
+	me.create = me.confid ? unused : true;
+
+	var ipanel = Ext.create('PVE.lxc.MountPointInputPanel', {
+	    confid: me.confid,
+	    nodename: nodename,
+	    unused: unused,
+	    create: me.create,
+	});
+
+	var subject;
+	if (unused) {
+	    subject = gettext('Unused Mount Point');
+	} else if (me.create) {
+	    subject = gettext('Mount Point');
+	} else {
+	    subject = gettext('Mount Point') + ' (' + me.confid + ')';
+	}
+
+	Ext.apply(me, {
+	    subject: subject,
+	    items: ipanel,
+	});
+
+	me.callParent();
+
+	me.load({
+	    success: function(response, options) {
+		ipanel.setVMConfig(response.result.data);
+		if (me.confid) {
+		    var value = response.result.data[me.confid];
+		    var mp = PVE.Parser.parseLxcMountPoint(value);
+
+		    if (!mp) {
+			Ext.Msg.alert(gettext('Error'), gettext('Unable to parse mount point options'));
+			me.close();
+			return;
+		    }
+
+		    ipanel.setMountPoint(mp);
+		    me.isValid(); // trigger validation
+		}
+	    }
+	});
+    }
+});
+
 Ext.define('PVE.lxc.CPUInputPanel', {
     extend: 'PVE.panel.InputPanel',
     alias: 'widget.pveLxcCPUInputPanel',
@@ -120,3 +179,282 @@ Ext.define('PVE.lxc.MemoryInputPanel', {
 	me.callParent();
     }
 });
+
+Ext.define('PVE.lxc.MountPointInputPanel', {
+    extend: 'PVE.panel.InputPanel',
+    alias: 'widget.pveLxcMountPointInputPanel',
+
+    insideWizard: false,
+
+    unused: false, // ADD usused disk imaged
+
+    vmconfig: {}, // used to select usused disks
+
+    onGetValues: function(values) {
+	var me = this;
+
+	var confid = me.confid || values.mpsel;
+
+	if (me.unused) {
+	    me.mpdata.file = me.vmconfig[values.unusedId];
+	    confid = values.mpsel;
+	} else if (me.create) {
+	    me.mpdata.file = values.storage + ':' + values.disksize;
+	}
+
+	if (confid !== 'rootfs')
+	    me.mpdata.mp = values.mp;
+
+	if (values.ro)
+	    me.mpdata.ro = 1;
+	else
+	    delete me.mpdata.ro;
+
+	if (values.quota)
+	    me.mpdata.quota = 1;
+	else
+	    delete me.mpdata.quota;
+
+	if (values.acl === 'Default')
+	    delete me.mpdata.acl;
+	else
+	    me.mpdata.acl = values.acl;
+
+	var res = {};
+	res[confid] = PVE.Parser.printLxcMountPoint(me.mpdata);
+	return res;
+    },
+
+    setMountPoint: function(mp) {
+	var me = this;
+
+	me.mpdata = mp;
+	if (!Ext.isDefined(me.mpdata['acl'])) {
+	    me.mpdata['acl'] = 'Default';
+	}
+
+	if (mp.type === 'bind') {
+	    me.quota.setDisabled(true);
+	    me.quota.setValue(false);
+	}
+
+	me.setValues(mp);
+    },
+
+    setVMConfig: function(vmconfig) {
+	var me = this;
+
+	me.vmconfig = vmconfig;
+
+	if (me.mpsel) {
+	    for (var i = 0; i != 8; ++i) {
+		var name = "mp" + i;
+		if (!Ext.isDefined(vmconfig[name])) {
+		    me.mpsel.setValue(name);
+		    break;
+		}
+	    }
+	}
+
+	if (me.unusedDisks) {
+	    var disklist = [];
+	    Ext.Object.each(vmconfig, function(key, value) {
+		if (key.match(/^unused\d+$/)) {
+		    disklist.push([key, value]);
+		}
+	    });
+	    me.unusedDisks.store.loadData(disklist);
+	    me.unusedDisks.setValue(me.confid);
+	}
+    },
+
+    setNodename: function(nodename) {
+	var me = this;
+	me.hdstoragesel.setNodename(nodename);
+	me.hdfilesel.setStorage(undefined, nodename);
+    },
+
+    initComponent : function() {
+	var me = this;
+
+	var isroot = me.confid === 'rootfs';
+
+	me.mpdata = {};
+
+	me.column1 = [];
+
+	if (!me.confid || me.unused) {
+	    var names = [];
+	    for (var i = 0; i != 8; ++i) {
+		var name = 'mp' + i;
+		names.push([name, name]);
+	    }
+	    me.mpsel = Ext.create('PVE.form.KVComboBox', {
+		name: 'mpsel',
+		fieldLabel: gettext('Mount Point'),
+		matchFieldWidth: false,
+		allowBlank: false,
+		data: names,
+		validator: function(value) {
+		    if (!me.rendered)
+			return;
+		    if (Ext.isDefined(me.vmconfig[value]))
+			return "Mount point is already in use.";
+		    return true;
+		},
+		listeners: {
+		    change: function(field, value) {
+			field.validate();
+		    }
+		},
+	    });
+	    me.column1.push(me.mpsel);
+	}
+
+	// we always have this around, but only visible when creating a new mp
+	// since this handles per-filesystem capabilities
+	me.hdstoragesel = Ext.create('PVE.form.StorageSelector', {
+	    name: 'storage',
+	    nodename: me.nodename,
+	    fieldLabel: gettext('Storage'),
+	    storageContent: 'rootdir',
+	    allowBlank: false,
+	    autoSelect: true,
+	    hidden: me.unused || !me.create,
+	    listeners: {
+		change: function(f, value) {
+		    if (me.mpdata.type === 'bind') {
+			me.quota.setDisabled(true);
+			me.quota.setValue(false);
+			return;
+		    }
+		    var rec = f.store.getById(value);
+		    if (rec.data.type === 'zfs' ||
+		        rec.data.type === 'zfspool') {
+			me.quota.setDisabled(true);
+			me.quota.setValue(false);
+		    } else {
+			me.quota.setDisabled(false);
+		    }
+		    if (me.unused || !me.create)
+			return;
+		    if (rec.data.type === 'iscsi') {
+			me.hdfilesel.setStorage(value);
+			me.hdfilesel.setDisabled(false);
+			me.hdfilesel.setVisible(true);
+			me.hdsizesel.setDisabled(true);
+			me.hdsizesel.setVisible(false);
+		    } else if (rec.data.type === 'lvm' ||
+			       rec.data.type === 'lvmthin' ||
+			       rec.data.type === 'rbd' ||
+			       rec.data.type === 'sheepdog' ||
+			       rec.data.type === 'zfs' ||
+			       rec.data.type === 'zfspool') {
+			me.hdfilesel.setDisabled(true);
+			me.hdfilesel.setVisible(false);
+			me.hdsizesel.setDisabled(false);
+			me.hdsizesel.setVisible(true);
+		    } else {
+			me.hdfilesel.setDisabled(true);
+			me.hdfilesel.setVisible(false);
+			me.hdsizesel.setDisabled(false);
+			me.hdsizesel.setVisible(true);
+		    }
+		},
+	    },
+	});
+	me.column1.push(me.hdstoragesel);
+
+	if (me.unused) {
+	    me.unusedDisks = Ext.create('PVE.form.KVComboBox', {
+		name: 'unusedId',
+		fieldLabel: gettext('Disk image'),
+		matchFieldWidth: false,
+		listConfig: {
+		    width: 350
+		},
+		data: [],
+		allowBlank: false,
+		listeners: {
+		    change: function(f, value) {
+			// make sure our buttons are enabled/disabled when switching
+			// between images on different storages:
+			var disk = me.vmconfig[value];
+			var storage = disk.split(':')[0];
+			me.hdstoragesel.setValue(storage);
+		    },
+		},
+	    });
+	    me.column1.push(me.unusedDisks);
+	} else if (me.create) {
+	    me.hdfilesel = Ext.create('PVE.form.FileSelector', {
+		name: 'file',
+		nodename: me.nodename,
+		storageContent: 'images',
+		fieldLabel: gettext('Disk image'),
+		disabled: true,
+		hidden: true,
+		allowBlank: false
+	    });
+	    me.hdsizesel = Ext.createWidget('numberfield', {
+		name: 'disksize',
+		minValue: 0.1,
+		maxValue: 128*1024,
+		decimalPrecision: 3,
+		value: '8',
+		step: 1,
+		fieldLabel: gettext('Disk size') + ' (GB)',
+		allowBlank: false
+	    });
+	    me.column1.push(me.hdfilesel);
+	    me.column1.push(me.hdsizesel);
+	} else {
+	    me.column1.push({
+		xtype: 'textfield',
+		disabled: true,
+		submitValue: false,
+		fieldLabel: gettext('Disk image'),
+		name: 'file'
+	    });
+	}
+
+	me.quota = Ext.createWidget('pvecheckbox', {
+	    name: 'quota',
+	    defaultValue: 0,
+	    fieldLabel: gettext('Enable quota'),
+	});
+
+	me.column2 = [
+	    {
+		xtype: 'pvecheckbox',
+		name: 'ro',
+		defaultValue: 0,
+		fieldLabel: gettext('Read-only'),
+		hidden: me.insideWizard,
+	    },
+	    {
+		xtype: 'pveKVComboBox',
+		name: 'acl',
+		fieldLabel: gettext('ACLs'),
+		data: [['Default', 'Default'], ['1', 'On'], ['0', 'Off']],
+		value: 'Default',
+		allowBlank: true,
+	    },
+	    me.quota,
+	];
+
+	if (!isroot) {
+	    me.column2.push({
+		xtype: 'textfield',
+		name: 'mp',
+		value: '',
+		emptyText: gettext('Path'),
+		allowBlank: false,
+		hidden: isroot,
+		fieldLabel: gettext('/some/path'),
+	    });
+	};
+
+	me.callParent();
+    }
+});
diff --git a/www/manager/lxc/Resources.js b/www/manager/lxc/Resources.js
index 7149ff0..396b6f9 100644
--- a/www/manager/lxc/Resources.js
+++ b/www/manager/lxc/Resources.js
@@ -35,6 +35,8 @@ Ext.define('PVE.lxc.RessourceView', {
 
 	var caps = Ext.state.Manager.get('GuiCap');
 
+	var mpeditor = caps.vms['VM.Config.Disk'] ? 'PVE.lxc.MountPointEdit' : undefined;
+
 	var rows = {
 	    memory: {
 		header: gettext('Memory'),
@@ -77,6 +79,7 @@ Ext.define('PVE.lxc.RessourceView', {
 	    rootfs: {
 		header: gettext('Root Disk'),
 		defaultValue: PVE.Utils.noneText,
+		editor: mpeditor,
 		tdCls: 'pve-itype-icon-storage'
 	    }
 	};
@@ -86,10 +89,21 @@ Ext.define('PVE.lxc.RessourceView', {
 	    rows[confid] = {
 		group: 1,
 		tdCls: 'pve-itype-icon-storage',
+		editor: mpeditor,
 		header: gettext('Mount Point') + ' (' + confid +')',
 	    };
 	}
 
+	for (i = 0; i < 8; i++) {
+	    confid = "unused" + i;
+	    rows[confid] = {
+		group: 1,
+		tdCls: 'pve-itype-icon-storage',
+		editor: mpeditor,
+		header: gettext('Unused Disk') + ' ' + i,
+	    };
+	}
+
 	var reload = function() {
 	    me.rstore.load();
 	};
@@ -138,6 +152,23 @@ Ext.define('PVE.lxc.RessourceView', {
 	    win.on('destroy', reload);
 	};
 
+	var run_remove = 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 edit_btn = new PVE.button.Button({
 	    text: gettext('Edit'),
 	    selModel: sm,
@@ -159,12 +190,30 @@ Ext.define('PVE.lxc.RessourceView', {
 	    handler: run_resize
 	});
 
+	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) + "'");
+		if (rec.data.key.match(/^unused\d+$/)) {
+		    msg += " " + gettext('This will permanently erase all image data.');
+		}
+
+		return msg;
+	    },
+	    handler: run_remove
+	});
+
 	var set_button_status = function() {
 	    var sm = me.getSelectionModel();
 	    var rec = sm.getSelection()[0];
 
 	    if (!rec) {
 		edit_btn.disable();
+		remove_btn.disable();
 		resize_btn.disable();
 		return;
 	    }
@@ -176,6 +225,7 @@ Ext.define('PVE.lxc.RessourceView', {
 
 	    edit_btn.setDisabled(rec.data['delete'] || !rowdef.editor);
 
+	    remove_btn.setDisabled(!isDisk || rec.data.key === 'rootfs');
 	    resize_btn.setDisabled(!isDisk);
 
 	};
@@ -184,8 +234,31 @@ Ext.define('PVE.lxc.RessourceView', {
 	    url: '/api2/json/' + baseurl,
 	    selModel: sm,
 	    cwidth1: 170,
-	    tbar: [ edit_btn,
-		    resize_btn],
+	    tbar: [
+		{
+		    text: gettext('Add'),
+		    menu: new Ext.menu.Menu({
+			items: [
+			    {
+				text: gettext('Mount Point'),
+				iconCls: 'pve-itype-icon-storage',
+				disabled: !caps.vms['VM.Config.Disk'],
+				handler: function() {
+				    var win = Ext.create('PVE.lxc.MountPointEdit', {
+					url: '/api2/extjs/' + baseurl,
+					pveSelNode: me.pveSelNode
+				    });
+				    win.on('destroy', reload);
+				    win.show();
+				}
+			    },
+			]
+		    })
+		},
+		edit_btn,
+		remove_btn,
+		resize_btn,
+	    ],
 	    rows: rows,
 	    listeners: {
 		show: reload,
-- 
2.1.4





More information about the pve-devel mailing list