mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-08-06 22:42:30 +00:00
lxc: create/edit/remove mountpoints
This commit is contained in:
parent
ae8a861e5b
commit
946eb8cd89
@ -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', {
|
Ext.define('PVE.lxc.CPUInputPanel', {
|
||||||
extend: 'PVE.panel.InputPanel',
|
extend: 'PVE.panel.InputPanel',
|
||||||
alias: 'widget.pveLxcCPUInputPanel',
|
alias: 'widget.pveLxcCPUInputPanel',
|
||||||
@ -120,3 +179,282 @@ Ext.define('PVE.lxc.MemoryInputPanel', {
|
|||||||
me.callParent();
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@ -35,6 +35,8 @@ Ext.define('PVE.lxc.RessourceView', {
|
|||||||
|
|
||||||
var caps = Ext.state.Manager.get('GuiCap');
|
var caps = Ext.state.Manager.get('GuiCap');
|
||||||
|
|
||||||
|
var mpeditor = caps.vms['VM.Config.Disk'] ? 'PVE.lxc.MountPointEdit' : undefined;
|
||||||
|
|
||||||
var rows = {
|
var rows = {
|
||||||
memory: {
|
memory: {
|
||||||
header: gettext('Memory'),
|
header: gettext('Memory'),
|
||||||
@ -77,6 +79,7 @@ Ext.define('PVE.lxc.RessourceView', {
|
|||||||
rootfs: {
|
rootfs: {
|
||||||
header: gettext('Root Disk'),
|
header: gettext('Root Disk'),
|
||||||
defaultValue: PVE.Utils.noneText,
|
defaultValue: PVE.Utils.noneText,
|
||||||
|
editor: mpeditor,
|
||||||
tdCls: 'pve-itype-icon-storage'
|
tdCls: 'pve-itype-icon-storage'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -86,10 +89,21 @@ Ext.define('PVE.lxc.RessourceView', {
|
|||||||
rows[confid] = {
|
rows[confid] = {
|
||||||
group: 1,
|
group: 1,
|
||||||
tdCls: 'pve-itype-icon-storage',
|
tdCls: 'pve-itype-icon-storage',
|
||||||
|
editor: mpeditor,
|
||||||
header: gettext('Mount Point') + ' (' + confid +')',
|
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() {
|
var reload = function() {
|
||||||
me.rstore.load();
|
me.rstore.load();
|
||||||
};
|
};
|
||||||
@ -138,6 +152,23 @@ Ext.define('PVE.lxc.RessourceView', {
|
|||||||
win.on('destroy', reload);
|
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({
|
var edit_btn = new PVE.button.Button({
|
||||||
text: gettext('Edit'),
|
text: gettext('Edit'),
|
||||||
selModel: sm,
|
selModel: sm,
|
||||||
@ -159,12 +190,30 @@ Ext.define('PVE.lxc.RessourceView', {
|
|||||||
handler: run_resize
|
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 set_button_status = function() {
|
||||||
var sm = me.getSelectionModel();
|
var sm = me.getSelectionModel();
|
||||||
var rec = sm.getSelection()[0];
|
var rec = sm.getSelection()[0];
|
||||||
|
|
||||||
if (!rec) {
|
if (!rec) {
|
||||||
edit_btn.disable();
|
edit_btn.disable();
|
||||||
|
remove_btn.disable();
|
||||||
resize_btn.disable();
|
resize_btn.disable();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -176,6 +225,7 @@ Ext.define('PVE.lxc.RessourceView', {
|
|||||||
|
|
||||||
edit_btn.setDisabled(rec.data['delete'] || !rowdef.editor);
|
edit_btn.setDisabled(rec.data['delete'] || !rowdef.editor);
|
||||||
|
|
||||||
|
remove_btn.setDisabled(!isDisk || rec.data.key === 'rootfs');
|
||||||
resize_btn.setDisabled(!isDisk);
|
resize_btn.setDisabled(!isDisk);
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -184,8 +234,31 @@ Ext.define('PVE.lxc.RessourceView', {
|
|||||||
url: '/api2/json/' + baseurl,
|
url: '/api2/json/' + baseurl,
|
||||||
selModel: sm,
|
selModel: sm,
|
||||||
cwidth1: 170,
|
cwidth1: 170,
|
||||||
tbar: [ edit_btn,
|
tbar: [
|
||||||
resize_btn],
|
{
|
||||||
|
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,
|
rows: rows,
|
||||||
listeners: {
|
listeners: {
|
||||||
show: reload,
|
show: reload,
|
||||||
|
Loading…
Reference in New Issue
Block a user