From: Aaron Lauterer <a.lauterer@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH v3 manager 1/4] ui: lxc/qemu: add disk reassign and action submenu
Date: Mon, 7 Mar 2022 11:07:19 +0100 [thread overview]
Message-ID: <20220307100722.257128-2-a.lauterer@proxmox.com> (raw)
In-Reply-To: <20220307100722.257128-1-a.lauterer@proxmox.com>
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@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
next prev parent reply other threads:[~2022-03-07 10:08 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-03-07 10:07 [pve-devel] [PATCH v3 manager 0/4] ui: lxc/qemu: add reassign for disks and volumes Aaron Lauterer
2022-03-07 10:07 ` Aaron Lauterer [this message]
2022-03-10 10:49 ` [pve-devel] [PATCH v3 manager 1/4] ui: lxc/qemu: add disk reassign and action submenu Fabian Ebner
2022-03-11 14:38 ` Aaron Lauterer
2022-03-14 8:18 ` Fabian Ebner
2022-03-14 8:19 ` Aaron Lauterer
2022-03-07 10:07 ` [pve-devel] [PATCH v3 manager 2/4] ui: lxc/qemu: disk/volume action simplify menu items Aaron Lauterer
2022-03-07 10:07 ` [pve-devel] [PATCH v3 manager 3/4] ui: BusTypeSelector: change noVirtIO to withVirtIO Aaron Lauterer
2022-03-10 11:24 ` Fabian Ebner
2022-03-07 10:07 ` [pve-devel] [PATCH v3 manager 4/4] ui: hdmove: modernize/refactor Aaron Lauterer
2022-03-10 11:19 ` Fabian Ebner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220307100722.257128-2-a.lauterer@proxmox.com \
--to=a.lauterer@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.