[pve-devel] [PATCH v3 manager 1/4] ui: lxc/qemu: add disk reassign and action submenu
Aaron Lauterer
a.lauterer at proxmox.com
Mon Mar 7 11:07:19 CET 2022
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 submenu.
Signed-off-by: Aaron Lauterer <a.lauterer at proxmox.com>
---
changes since
v2:
* switch from generic to Proxmox Edit window
* add new submenu for disk/volume specific actions
* code style improvements
* simplify some labels, removing "disk" and "volume" as the context
already gives this away
v1: incorporated feedback I got off list
* use more modern approaches
* arrow functions
* autoShow
* template strings
* reduce predefined cbind values and use arrow functions in the cbind
directly in many cases
* some code style issues and cleanup
www/manager6/Makefile | 1 +
www/manager6/lxc/Resources.js | 62 +++++--
www/manager6/qemu/HDReassign.js | 274 ++++++++++++++++++++++++++++++
www/manager6/qemu/HardwareView.js | 60 ++++++-
4 files changed, 378 insertions(+), 19 deletions(-)
create mode 100644 www/manager6/qemu/HDReassign.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index e6e01bd1..94a78d89 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -214,6 +214,7 @@ JSSRC= \
qemu/HDTPM.js \
qemu/HDMove.js \
qemu/HDResize.js \
+ qemu/HDReassign.js \
qemu/HardwareView.js \
qemu/IPConfigEdit.js \
qemu/KeyboardEdit.js \
diff --git a/www/manager6/lxc/Resources.js b/www/manager6/lxc/Resources.js
index 15ee3c67..306a4988 100644
--- a/www/manager6/lxc/Resources.js
+++ b/www/manager6/lxc/Resources.js
@@ -151,7 +151,8 @@ Ext.define('PVE.lxc.RessourceView', {
});
};
- var run_move = function(b, e, rec) {
+ var run_move = function() {
+ let rec = me.selModel.getSelection()[0];
if (!rec) {
return;
}
@@ -168,6 +169,24 @@ Ext.define('PVE.lxc.RessourceView', {
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({
text: gettext('Edit'),
selModel: me.selModel,
@@ -182,7 +201,7 @@ Ext.define('PVE.lxc.RessourceView', {
handler: function() { me.run_editor(); },
});
- var resize_btn = new Proxmox.button.Button({
+ var resize_menuitem = new Ext.menu.Item({
text: gettext('Resize disk'),
selModel: me.selModel,
disabled: true,
@@ -227,14 +246,34 @@ Ext.define('PVE.lxc.RessourceView', {
},
});
- var move_btn = new Proxmox.button.Button({
+ let reassign_menuitem = new Ext.menu.Item({
+ text: gettext('Reassign Volume'),
+ tooltip: gettext('Reassign volume to another CT'),
+ handler: run_reassign,
+ reference: 'reassing_item',
+ disabled: true,
+ });
+
+ let move_menuitem = new Ext.menu.Item({
text: gettext('Move Volume'),
selModel: me.selModel,
disabled: true,
- dangerous: true,
handler: run_move,
});
+ let volumeaction_btn = new Proxmox.button.Button({
+ text: gettext('Volume Action'),
+ disabled: true,
+ menu: {
+ plain: true,
+ items: [
+ move_menuitem,
+ reassign_menuitem,
+ resize_menuitem,
+ ],
+ },
+ });
+
var revert_btn = new PVE.button.PendingRevert();
var set_button_status = function() {
@@ -243,7 +282,7 @@ Ext.define('PVE.lxc.RessourceView', {
if (!rec) {
edit_btn.disable();
remove_btn.disable();
- resize_btn.disable();
+ volumeaction_btn.disable();
revert_btn.disable();
return;
}
@@ -253,6 +292,7 @@ Ext.define('PVE.lxc.RessourceView', {
var pending = rec.data.delete || me.hasPendingChanges(key);
var isDisk = rowdef.tdCls === 'pve-itype-icon-storage';
+ let isRootFS = rec.data.key === 'rootfs';
var isUnusedDisk = key.match(/^unused\d+/);
var isUsedDisk = isDisk && !isUnusedDisk;
@@ -265,9 +305,12 @@ Ext.define('PVE.lxc.RessourceView', {
}
edit_btn.setDisabled(noedit);
- remove_btn.setDisabled(!isDisk || rec.data.key === 'rootfs' || !diskCap || pending);
- resize_btn.setDisabled(!isDisk || !diskCap || isUnusedDisk);
- move_btn.setDisabled(!isDisk || !diskCap);
+ volumeaction_btn.setDisabled(!isDisk);
+ reassign_menuitem.setDisabled(isRootFS);
+
+ remove_btn.setDisabled(!isDisk || isRootFS || !diskCap || pending);
+ resize_menuitem.setDisabled(!isDisk || !diskCap || isUnusedDisk);
+ move_menuitem.setDisabled(!isDisk || !diskCap);
revert_btn.setDisabled(!pending);
remove_btn.setText(isUsedDisk ? remove_btn.altText : remove_btn.defaultText);
@@ -327,8 +370,7 @@ Ext.define('PVE.lxc.RessourceView', {
},
edit_btn,
remove_btn,
- resize_btn,
- move_btn,
+ volumeaction_btn,
revert_btn,
],
rows: rows,
diff --git a/www/manager6/qemu/HDReassign.js b/www/manager6/qemu/HDReassign.js
new file mode 100644
index 00000000..48bc0b1f
--- /dev/null
+++ b/www/manager6/qemu/HDReassign.js
@@ -0,0 +1,274 @@
+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: `/nodes/${me.nodename}/${me.type}/${me.vmid}/`,
+ };
+ },
+
+ cbind: {
+ title: get => get('isQemu') ? gettext('Reassign disk') : gettext('Reassign volume'),
+ submitText: get => get('title'),
+ qemu: '{isQemu}',
+ url: '{url}',
+ },
+
+ submitUrl: function(url, values) {
+ url += this.qemu ? 'move_disk' : 'move_volume';
+ return 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;
+
+ PVE.Utils.forEachMP((bus, i) => {
+ let name = view.getViewModel().get('mpType') + i.toString();
+ if (!Ext.isDefined(view.VMConfig[name])) {
+ mpIdSelector.setValue(i);
+ return false;
+ }
+ return undefined;
+ });
+
+ mpType.setDisabled(false);
+ mpIdSelector.setDisabled(false);
+ mpIdSelector.validate();
+ }
+ },
+ });
+ },
+ },
+
+ items: [
+ {
+ xtype: 'form',
+ reference: 'moveFormPanel',
+ bodyPadding: 10,
+ border: false,
+ fieldDefaults: {
+ labelWidth: 100,
+ anchor: '100%',
+ },
+ items: [
+ {
+ xtype: 'displayfield',
+ name: 'sourceDisk',
+ fieldLabel: gettext('Source'),
+ cbind: {
+ name: get => get('isQemu') ? 'disk' : 'volume',
+ value: '{disk}',
+ },
+ vtype: 'StorageId',
+ 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}',
+ },
+ },
+ ],
+ },
+ 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();
+ },
+});
diff --git a/www/manager6/qemu/HardwareView.js b/www/manager6/qemu/HardwareView.js
index 6cea4287..1f19269c 100644
--- a/www/manager6/qemu/HardwareView.js
+++ b/www/manager6/qemu/HardwareView.js
@@ -417,6 +417,24 @@ Ext.define('PVE.qemu.HardwareView', {
win.on('destroy', me.reload, me);
};
+ let run_reassign = function() {
+ let rec = sm.getSelection()[0];
+ if (!rec) {
+ return;
+ }
+
+ Ext.create('PVE.window.HDReassign', {
+ disk: rec.data.key,
+ nodename: nodename,
+ autoShow: true,
+ vmid: vmid,
+ type: 'qemu',
+ listeners: {
+ destroy: () => me.reload(),
+ },
+ });
+ };
+
var edit_btn = new Proxmox.button.Button({
text: gettext('Edit'),
selModel: sm,
@@ -424,20 +442,40 @@ Ext.define('PVE.qemu.HardwareView', {
handler: run_editor,
});
- var resize_btn = new Proxmox.button.Button({
+ var resize_menuitem = new Ext.menu.Item({
text: gettext('Resize disk'),
selModel: sm,
disabled: true,
handler: run_resize,
});
- var move_btn = new Proxmox.button.Button({
+ let move_menuitem = new Ext.menu.Item({
text: gettext('Move disk'),
selModel: sm,
- disabled: true,
handler: run_move,
});
+ let reassign_menuitem = new Ext.menu.Item({
+ text: gettext('Reassign Disk'),
+ tooltip: gettext('Reassign disk to another VM'),
+ handler: run_reassign,
+ selModel: sm,
+ });
+
+ let diskaction_btn = new Proxmox.button.Button({
+ text: gettext('Disk Action'),
+ disabled: true,
+ menu: {
+ plain: true,
+ items: [
+ move_menuitem,
+ reassign_menuitem,
+ resize_menuitem,
+ ],
+ },
+ });
+
+
var remove_btn = new Proxmox.button.Button({
text: gettext('Remove'),
defaultText: gettext('Remove'),
@@ -573,8 +611,7 @@ Ext.define('PVE.qemu.HardwareView', {
if (!rec) {
remove_btn.disable();
edit_btn.disable();
- resize_btn.disable();
- move_btn.disable();
+ diskaction_btn.disable();
revert_btn.disable();
return;
}
@@ -611,9 +648,15 @@ Ext.define('PVE.qemu.HardwareView', {
(isDisk && !diskCap),
);
- resize_btn.setDisabled(pending || !isUsedDisk || !diskCap);
+ resize_menuitem.setDisabled(pending || !isUsedDisk || !diskCap);
+ reassign_menuitem.setDisabled(pending || (isEfi || tpmMoveable));
- move_btn.setDisabled(pending || !(isUsedDisk || isEfi || tpmMoveable) || !diskCap);
+ diskaction_btn.setDisabled(
+ pending ||
+ isCloudInit ||
+ !(isDisk || isEfi || tpmMoveable) ||
+ !diskCap,
+ );
revert_btn.setDisabled(!pending);
};
@@ -775,8 +818,7 @@ Ext.define('PVE.qemu.HardwareView', {
},
remove_btn,
edit_btn,
- resize_btn,
- move_btn,
+ diskaction_btn,
revert_btn,
],
rows: rows,
--
2.30.2
More information about the pve-devel
mailing list