ui: lxc/qemu: add disk reassign and action submenu

For the new HDReassign component, we follow the approach of HDMove to
have one componend for qemu and lxc.

To avoid button clutter, a new "Disk/Volume action" button is
introduced. It holds the Move, Reassign and Resize buttons in a
sub-menu.

Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Reviewed-by: Fabian Ebner <f.ebner@proxmox.com>
This commit is contained in:
Aaron Lauterer 2022-04-05 14:30:14 +02:00 committed by Thomas Lamprecht
parent 000b5537fc
commit a8d854afe2
4 changed files with 403 additions and 46 deletions

View File

@ -213,6 +213,7 @@ JSSRC= \
qemu/HDEfi.js \ qemu/HDEfi.js \
qemu/HDTPM.js \ qemu/HDTPM.js \
qemu/HDMove.js \ qemu/HDMove.js \
qemu/HDReassign.js \
qemu/HDResize.js \ qemu/HDResize.js \
qemu/HardwareView.js \ qemu/HardwareView.js \
qemu/IPConfigEdit.js \ qemu/IPConfigEdit.js \

View File

@ -162,7 +162,8 @@ Ext.define('PVE.lxc.RessourceView', {
}); });
}; };
var run_move = function(b, e, rec) { let run_move = function() {
let rec = me.selModel.getSelection()[0];
if (!rec) { if (!rec) {
return; return;
} }
@ -179,6 +180,24 @@ Ext.define('PVE.lxc.RessourceView', {
win.on('destroy', me.reload, me); win.on('destroy', me.reload, me);
}; };
let run_reassign = function() {
let rec = me.selModel.getSelection()[0];
if (!rec) {
return;
}
Ext.create('PVE.window.HDReassign', {
disk: rec.data.key,
nodename: nodename,
autoShow: true,
vmid: vmid,
type: 'lxc',
listeners: {
destroy: () => me.reload(),
},
});
};
var edit_btn = new Proxmox.button.Button({ var edit_btn = new Proxmox.button.Button({
text: gettext('Edit'), text: gettext('Edit'),
selModel: me.selModel, selModel: me.selModel,
@ -193,13 +212,6 @@ Ext.define('PVE.lxc.RessourceView', {
handler: function() { me.run_editor(); }, handler: function() { me.run_editor(); },
}); });
var resize_btn = new Proxmox.button.Button({
text: gettext('Resize disk'),
selModel: me.selModel,
disabled: true,
handler: run_resize,
});
var remove_btn = new Proxmox.button.Button({ var remove_btn = new Proxmox.button.Button({
text: gettext('Remove'), text: gettext('Remove'),
defaultText: gettext('Remove'), defaultText: gettext('Remove'),
@ -238,14 +250,41 @@ Ext.define('PVE.lxc.RessourceView', {
}, },
}); });
var move_btn = new Proxmox.button.Button({ let move_menuitem = new Ext.menu.Item({
text: gettext('Move Volume'), text: gettext('Move Storage'),
tooltip: gettext('Move volume to another storage'),
iconCls: 'fa fa-database',
selModel: me.selModel, selModel: me.selModel,
disabled: true,
dangerous: true,
handler: run_move, handler: run_move,
}); });
let reassign_menuitem = new Ext.menu.Item({
text: gettext('Reassign Owner'),
tooltip: gettext('Reassign volume to another CT'),
iconCls: 'fa fa-cube',
handler: run_reassign,
reference: 'reassing_item',
});
let resize_menuitem = new Ext.menu.Item({
text: gettext('Resize'),
iconCls: 'fa fa-plus',
selModel: me.selModel,
handler: run_resize,
});
let volumeaction_btn = new Proxmox.button.Button({
text: gettext('Volume Action'),
disabled: true,
menu: {
items: [
move_menuitem,
reassign_menuitem,
resize_menuitem,
],
},
});
var revert_btn = new PVE.button.PendingRevert(); var revert_btn = new PVE.button.PendingRevert();
var set_button_status = function() { var set_button_status = function() {
@ -254,7 +293,7 @@ Ext.define('PVE.lxc.RessourceView', {
if (!rec) { if (!rec) {
edit_btn.disable(); edit_btn.disable();
remove_btn.disable(); remove_btn.disable();
resize_btn.disable(); volumeaction_btn.disable();
revert_btn.disable(); revert_btn.disable();
return; return;
} }
@ -263,7 +302,8 @@ Ext.define('PVE.lxc.RessourceView', {
var rowdef = rows[key]; var rowdef = rows[key];
var pending = rec.data.delete || me.hasPendingChanges(key); var pending = rec.data.delete || me.hasPendingChanges(key);
let isDisk = key === 'rootfs' || key.match(/^(mp|unused)\d+/); let isRootFS = key === 'rootfs';
let isDisk = isRootFS || key.match(/^(mp|unused)\d+/);
var isUnusedDisk = key.match(/^unused\d+/); var isUnusedDisk = key.match(/^unused\d+/);
var isUsedDisk = isDisk && !isUnusedDisk; var isUsedDisk = isDisk && !isUnusedDisk;
@ -276,9 +316,12 @@ Ext.define('PVE.lxc.RessourceView', {
} }
edit_btn.setDisabled(noedit); edit_btn.setDisabled(noedit);
remove_btn.setDisabled(!isDisk || rec.data.key === 'rootfs' || !diskCap || pending); volumeaction_btn.setDisabled(!isDisk || !diskCap);
resize_btn.setDisabled(!isDisk || !diskCap || isUnusedDisk); move_menuitem.setDisabled(isUnusedDisk);
move_btn.setDisabled(!isDisk || !diskCap); reassign_menuitem.setDisabled(isRootFS);
resize_menuitem.setDisabled(isUnusedDisk);
remove_btn.setDisabled(!isDisk || isRootFS || !diskCap || pending);
revert_btn.setDisabled(!pending); revert_btn.setDisabled(!pending);
remove_btn.setText(isUsedDisk ? remove_btn.altText : remove_btn.defaultText); remove_btn.setText(isUsedDisk ? remove_btn.altText : remove_btn.defaultText);
@ -340,8 +383,7 @@ Ext.define('PVE.lxc.RessourceView', {
}, },
edit_btn, edit_btn,
remove_btn, remove_btn,
resize_btn, volumeaction_btn,
move_btn,
revert_btn, revert_btn,
], ],
rows: rows, rows: rows,

View File

@ -0,0 +1,272 @@
Ext.define('PVE.window.HDReassign', {
extend: 'Proxmox.window.Edit',
mixins: ['Proxmox.Mixin.CBind'],
resizable: false,
modal: true,
width: 350,
border: false,
layout: 'fit',
showReset: false,
showProgress: true,
method: 'POST',
viewModel: {
data: {
mpType: '',
},
formulas: {
mpMaxCount: get => get('mpType') === 'mp'
? PVE.Utils.mp_counts.mps - 1
: PVE.Utils.mp_counts.unused - 1,
},
},
cbindData: function() {
let me = this;
return {
vmid: me.vmid,
disk: me.disk,
isQemu: me.type === 'qemu',
nodename: me.nodename,
url: () => {
let endpoint = me.type === 'qemu' ? 'move_disk' : 'move_volume';
return `/nodes/${me.nodename}/${me.type}/${me.vmid}/${endpoint}`;
},
};
},
cbind: {
title: get => get('isQemu') ? gettext('Reassign disk') : gettext('Reassign volume'),
submitText: get => get('title'),
qemu: '{isQemu}',
url: '{url}',
},
getValues: function() {
let me = this;
let values = me.formPanel.getForm().getValues();
let params = {
vmid: me.vmid,
'target-vmid': values.targetVmid,
};
params[me.qemu ? 'disk' : 'volume'] = me.disk;
if (me.qemu) {
params['target-disk'] = `${values.controller}${values.deviceid}`;
} else {
params['target-volume'] = `${values.mpType}${values.mpId}`;
}
return params;
},
controller: {
xclass: 'Ext.app.ViewController',
initViewModel: function(model) {
let view = this.getView();
let mpTypeValue = view.disk.match(/^unused\d+/) ? 'unused' : 'mp';
model.set('mpType', mpTypeValue);
},
onMpTypeChange: function(value) {
this.getView().getViewModel().set('mpType', value.getValue());
this.getView().lookup('mpIdSelector').validate();
},
onTargetVMChange: function(f, vmid) {
let me = this;
let view = me.getView();
let diskSelector = view.lookup('diskSelector');
if (!vmid) {
diskSelector.setVMConfig(null);
me.VMConfig = null;
return;
}
let type = view.qemu ? 'qemu' : 'lxc';
let url = `/nodes/${view.nodename}/${type}/${vmid}/config`;
Proxmox.Utils.API2Request({
url: url,
method: 'GET',
failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
success: function(response, options) {
if (view.qemu) {
diskSelector.setVMConfig(response.result.data);
diskSelector.setDisabled(false);
} else {
let mpIdSelector = view.lookup('mpIdSelector');
let mpType = view.lookup('mpType');
view.VMConfig = response.result.data;
mpIdSelector.setValue(
PVE.Utils.nextFreeMP(
view.getViewModel().get('mpType'),
view.VMConfig,
).id,
);
mpType.setDisabled(false);
mpIdSelector.setDisabled(false);
mpIdSelector.validate();
}
},
});
},
},
items: [
{
xtype: 'form',
reference: 'moveFormPanel',
border: false,
fieldDefaults: {
labelWidth: 100,
anchor: '100%',
},
items: [
{
xtype: 'displayfield',
name: 'sourceDisk',
fieldLabel: gettext('Source'),
cbind: {
name: get => get('isQemu') ? 'disk' : 'volume',
value: '{disk}',
},
allowBlank: false,
},
{
xtype: 'vmComboSelector',
reference: 'targetVMID',
name: 'targetVmid',
allowBlank: false,
fieldLabel: gettext('Target'),
bind: {
value: '{targetVMID}',
},
store: {
model: 'PVEResources',
autoLoad: true,
sorters: 'vmid',
cbind: {}, // for nested cbinds
filters: [
{
property: 'type',
cbind: {
value: get => get('isQemu') ? 'qemu' : 'lxc',
},
},
{
property: 'node',
cbind: {
value: '{nodename}',
},
},
{
property: 'vmid',
operator: '!=',
cbind: {
value: '{vmid}',
},
},
{
property: 'template',
value: 0,
},
],
},
listeners: { change: 'onTargetVMChange' },
},
{
xtype: 'pveControllerSelector',
reference: 'diskSelector',
withUnused: true,
disabled: true,
cbind: {
hidden: '{!isQemu}',
},
},
{
xtype: 'container',
layout: 'hbox',
cbind: {
hidden: '{isQemu}',
disabled: '{isQemu}',
},
items: [
{
xtype: 'pmxDisplayEditField',
cbind: {
editable: get => !get('disk').match(/^unused\d+/),
value: get => get('disk').match(/^unused\d+/) ? 'unused' : 'mp',
},
disabled: true,
name: 'mpType',
reference: 'mpType',
fieldLabel: gettext('Add as'),
submitValue: true,
flex: 4,
editConfig: {
xtype: 'proxmoxKVComboBox',
name: 'mpTypeCombo',
reference: 'mpTypeCombo',
deleteEmpty: false,
cbind: {
hidden: '{isQemu}',
},
comboItems: [
['mp', gettext('Mount Point')],
['unused', gettext('Unused')],
],
listeners: { change: 'onMpTypeChange' },
},
},
{
xtype: 'proxmoxintegerfield',
name: 'mpId',
reference: 'mpIdSelector',
minValue: 0,
flex: 1,
allowBlank: false,
validateOnChange: true,
disabled: true,
bind: {
maxValue: '{mpMaxCount}',
},
validator: function(value) {
let view = this.up('window');
let type = view.getViewModel().get('mpType');
if (Ext.isDefined(view.VMConfig[`${type}${value}`])) {
return "Mount point is already in use.";
}
return true;
},
},
],
},
],
},
],
initComponent: function() {
let me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
if (!me.type) {
throw "no type specified";
}
me.callParent();
},
});

View File

@ -377,10 +377,56 @@ Ext.define('PVE.qemu.HardwareView', {
handler: run_editor, handler: run_editor,
}); });
let resize_btn = new Proxmox.button.Button({ let move_menuitem = new Ext.menu.Item({
text: gettext('Resize disk'), text: gettext('Move Storage'),
tooltip: gettext('Move disk to another storage'),
iconCls: 'fa fa-database',
selModel: sm,
handler: () => {
let rec = sm.getSelection()[0];
if (!rec) {
return;
}
Ext.create('PVE.window.HDMove', {
autoShow: true,
disk: rec.data.key,
nodename: nodename,
vmid: vmid,
listeners: {
destroy: () => me.reload(),
},
});
},
});
let reassign_menuitem = new Ext.menu.Item({
text: gettext('Reassign Owner'),
tooltip: gettext('Reassign disk to another VM'),
iconCls: 'fa fa-desktop',
selModel: sm,
handler: () => {
let rec = sm.getSelection()[0];
if (!rec) {
return;
}
Ext.create('PVE.window.HDReassign', {
autoShow: true,
disk: rec.data.key,
nodename: nodename,
vmid: vmid,
type: 'qemu',
listeners: {
destroy: () => me.reload(),
},
});
},
});
let resize_menuitem = new Ext.menu.Item({
text: gettext('Resize'),
iconCls: 'fa fa-plus',
selModel: sm, selModel: sm,
disabled: true,
handler: () => { handler: () => {
let rec = sm.getSelection()[0]; let rec = sm.getSelection()[0];
if (!rec) { if (!rec) {
@ -398,27 +444,19 @@ Ext.define('PVE.qemu.HardwareView', {
}, },
}); });
let move_btn = new Proxmox.button.Button({ let diskaction_btn = new Proxmox.button.Button({
text: gettext('Move disk'), text: gettext('Disk Action'),
selModel: sm,
disabled: true, disabled: true,
handler: () => { menu: {
var rec = sm.getSelection()[0]; items: [
if (!rec) { move_menuitem,
return; reassign_menuitem,
} resize_menuitem,
Ext.create('PVE.window.HDMove', { ],
autoShow: true,
disk: rec.data.key,
nodename: nodename,
vmid: vmid,
listeners: {
destroy: () => me.reload(),
},
});
}, },
}); });
let remove_btn = new Proxmox.button.Button({ let remove_btn = new Proxmox.button.Button({
text: gettext('Remove'), text: gettext('Remove'),
defaultText: gettext('Remove'), defaultText: gettext('Remove'),
@ -544,8 +582,7 @@ Ext.define('PVE.qemu.HardwareView', {
if (!rec) { if (!rec) {
remove_btn.disable(); remove_btn.disable();
edit_btn.disable(); edit_btn.disable();
resize_btn.disable(); diskaction_btn.disable();
move_btn.disable();
revert_btn.disable(); revert_btn.disable();
return; return;
} }
@ -572,9 +609,15 @@ Ext.define('PVE.qemu.HardwareView', {
edit_btn.setDisabled( edit_btn.setDisabled(
deleted || !row.editor || isCloudInit || (isCDRom && !cdromCap) || (isDisk && !diskCap)); deleted || !row.editor || isCloudInit || (isCDRom && !cdromCap) || (isDisk && !diskCap));
resize_btn.setDisabled(pending || !isUsedDisk || !diskCap); diskaction_btn.setDisabled(
pending ||
move_btn.setDisabled(pending || !(isUsedDisk || isEfi || tpmMoveable) || !diskCap); !diskCap ||
isCloudInit ||
!(isDisk || isEfi || tpmMoveable),
);
move_menuitem.setDisabled(isUnusedDisk);
reassign_menuitem.setDisabled(pending || (isEfi || tpmMoveable));
resize_menuitem.setDisabled(pending || !isUsedDisk);
revert_btn.setDisabled(!pending); revert_btn.setDisabled(!pending);
}; };
@ -679,8 +722,7 @@ Ext.define('PVE.qemu.HardwareView', {
}, },
remove_btn, remove_btn,
edit_btn, edit_btn,
resize_btn, diskaction_btn,
move_btn,
revert_btn, revert_btn,
], ],
rows: rows, rows: rows,