From: Aaron Lauterer <a.lauterer@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH v6 manager 3/5] ui: lxc/qemu: add disk reassign and action submenu
Date: Tue, 5 Apr 2022 14:30:14 +0200 [thread overview]
Message-ID: <20220405123016.2911726-4-a.lauterer@proxmox.com> (raw)
In-Reply-To: <20220405123016.2911726-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
v5: rebased
v4:
* use button labels and icons as suggested by @Thomas
only for Move Storage I went with the same Icon we use for the
storages. That should make the connection clearer since we are also
using the LXC/VM icons for the reassign buttons.
* smaller style nits like reordering button/menuitem definitions to be
grouped closer together
* reworked logic when to disable buttons/menuitems
v3:
* use PVE.Util.nextFreeMP
* create 'url' in one place, no need for submitURL
* filter out templates in target guest selection
* fix padding
* code cleanup changes
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
www/manager6/Makefile | 1 +
www/manager6/lxc/Resources.js | 78 +++++++--
www/manager6/qemu/HDReassign.js | 272 ++++++++++++++++++++++++++++++
www/manager6/qemu/HardwareView.js | 74 ++++++--
4 files changed, 391 insertions(+), 34 deletions(-)
create mode 100644 www/manager6/qemu/HDReassign.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index e6e01bd1..a7101553 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -213,6 +213,7 @@ JSSRC= \
qemu/HDEfi.js \
qemu/HDTPM.js \
qemu/HDMove.js \
+ qemu/HDReassign.js \
qemu/HDResize.js \
qemu/HardwareView.js \
qemu/IPConfigEdit.js \
diff --git a/www/manager6/lxc/Resources.js b/www/manager6/lxc/Resources.js
index 683be526..9ca33f17 100644
--- a/www/manager6/lxc/Resources.js
+++ b/www/manager6/lxc/Resources.js
@@ -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) {
return;
}
@@ -179,6 +180,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,
@@ -193,13 +212,6 @@ Ext.define('PVE.lxc.RessourceView', {
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({
text: gettext('Remove'),
defaultText: gettext('Remove'),
@@ -238,14 +250,41 @@ Ext.define('PVE.lxc.RessourceView', {
},
});
- var move_btn = new Proxmox.button.Button({
- text: gettext('Move Volume'),
+ let move_menuitem = new Ext.menu.Item({
+ text: gettext('Move Storage'),
+ tooltip: gettext('Move volume to another storage'),
+ iconCls: 'fa fa-database',
selModel: me.selModel,
- disabled: true,
- dangerous: true,
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 set_button_status = function() {
@@ -254,7 +293,7 @@ Ext.define('PVE.lxc.RessourceView', {
if (!rec) {
edit_btn.disable();
remove_btn.disable();
- resize_btn.disable();
+ volumeaction_btn.disable();
revert_btn.disable();
return;
}
@@ -264,6 +303,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;
@@ -276,9 +316,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 || !diskCap);
+ move_menuitem.setDisabled(isUnusedDisk);
+ reassign_menuitem.setDisabled(isRootFS);
+ resize_menuitem.setDisabled(isUnusedDisk);
+
+ remove_btn.setDisabled(!isDisk || isRootFS || !diskCap || pending);
revert_btn.setDisabled(!pending);
remove_btn.setText(isUsedDisk ? remove_btn.altText : remove_btn.defaultText);
@@ -340,8 +383,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..b6c67964
--- /dev/null
+++ b/www/manager6/qemu/HDReassign.js
@@ -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();
+ },
+});
diff --git a/www/manager6/qemu/HardwareView.js b/www/manager6/qemu/HardwareView.js
index e3506e4d..749d355f 100644
--- a/www/manager6/qemu/HardwareView.js
+++ b/www/manager6/qemu/HardwareView.js
@@ -377,16 +377,17 @@ Ext.define('PVE.qemu.HardwareView', {
handler: run_editor,
});
- let resize_btn = new Proxmox.button.Button({
- text: gettext('Resize disk'),
+ let move_menuitem = new Ext.menu.Item({
+ text: gettext('Move Storage'),
+ tooltip: gettext('Move disk to another storage'),
+ iconCls: 'fa fa-database',
selModel: sm,
- disabled: true,
handler: () => {
let rec = sm.getSelection()[0];
if (!rec) {
return;
}
- Ext.create('PVE.window.HDResize', {
+ Ext.create('PVE.window.HDMove', {
autoShow: true,
disk: rec.data.key,
nodename: nodename,
@@ -398,20 +399,23 @@ Ext.define('PVE.qemu.HardwareView', {
},
});
- let move_btn = new Proxmox.button.Button({
- text: gettext('Move disk'),
+ let reassign_menuitem = new Ext.menu.Item({
+ text: gettext('Reassign Owner'),
+ tooltip: gettext('Reassign disk to another VM'),
+ iconCls: 'fa fa-desktop',
selModel: sm,
- disabled: true,
handler: () => {
- var rec = sm.getSelection()[0];
+ let rec = sm.getSelection()[0];
if (!rec) {
return;
}
- Ext.create('PVE.window.HDMove', {
+
+ Ext.create('PVE.window.HDReassign', {
autoShow: true,
disk: rec.data.key,
nodename: nodename,
vmid: vmid,
+ type: 'qemu',
listeners: {
destroy: () => me.reload(),
},
@@ -419,6 +423,40 @@ Ext.define('PVE.qemu.HardwareView', {
},
});
+ let resize_menuitem = new Ext.menu.Item({
+ text: gettext('Resize'),
+ iconCls: 'fa fa-plus',
+ selModel: sm,
+ handler: () => {
+ let rec = sm.getSelection()[0];
+ if (!rec) {
+ return;
+ }
+ Ext.create('PVE.window.HDResize', {
+ autoShow: true,
+ disk: rec.data.key,
+ nodename: nodename,
+ vmid: vmid,
+ listeners: {
+ destroy: () => me.reload(),
+ },
+ });
+ },
+ });
+
+ let diskaction_btn = new Proxmox.button.Button({
+ text: gettext('Disk Action'),
+ disabled: true,
+ menu: {
+ items: [
+ move_menuitem,
+ reassign_menuitem,
+ resize_menuitem,
+ ],
+ },
+ });
+
+
let remove_btn = new Proxmox.button.Button({
text: gettext('Remove'),
defaultText: gettext('Remove'),
@@ -544,8 +582,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;
}
@@ -572,9 +609,15 @@ Ext.define('PVE.qemu.HardwareView', {
edit_btn.setDisabled(
deleted || !row.editor || isCloudInit || (isCDRom && !cdromCap) || (isDisk && !diskCap));
- resize_btn.setDisabled(pending || !isUsedDisk || !diskCap);
-
- move_btn.setDisabled(pending || !(isUsedDisk || isEfi || tpmMoveable) || !diskCap);
+ diskaction_btn.setDisabled(
+ pending ||
+ !diskCap ||
+ isCloudInit ||
+ !(isDisk || isEfi || tpmMoveable),
+ );
+ move_menuitem.setDisabled(isUnusedDisk);
+ reassign_menuitem.setDisabled(pending || (isEfi || tpmMoveable));
+ resize_menuitem.setDisabled(pending || !isUsedDisk);
revert_btn.setDisabled(!pending);
};
@@ -679,8 +722,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-04-05 12:30 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-04-05 12:30 [pve-devel] [PATCH v6 manager 0/5] ui: lxc/qemu: disk/volume action simplify menu items Aaron Lauterer
2022-04-05 12:30 ` [pve-devel] [PATCH v6 manager 1/5] ui: utils: refactor mps to mp Aaron Lauterer
2022-04-05 12:30 ` [pve-devel] [PATCH v6 manager 2/5] ui: utils: add nextFreeMP Aaron Lauterer
2022-04-07 12:30 ` Fabian Ebner
2022-04-05 12:30 ` Aaron Lauterer [this message]
2022-04-07 12:23 ` [pve-devel] [PATCH v6 manager 3/5] ui: lxc/qemu: add disk reassign and action submenu Fabian Ebner
2022-04-05 12:30 ` [pve-devel] [PATCH v6 manager 4/5] ui: BusTypeSelector: change noVirtIO to withVirtIO Aaron Lauterer
2022-04-05 12:30 ` [pve-devel] [PATCH v6 manager 5/5] ui: hdmove: modernize/refactor Aaron Lauterer
2022-04-07 12:28 ` [pve-devel] [PATCH v6 manager 0/5] ui: lxc/qemu: disk/volume action simplify menu items Fabian Ebner
2022-04-11 10:06 ` [pve-devel] applied-series: " Thomas Lamprecht
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=20220405123016.2911726-4-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.