]> git.proxmox.com Git - pve-manager.git/commitdiff
ui: lxc/qemu: add disk reassign and action submenu
authorAaron Lauterer <a.lauterer@proxmox.com>
Tue, 5 Apr 2022 12:30:14 +0000 (14:30 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Thu, 7 Apr 2022 13:34:44 +0000 (15:34 +0200)
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>
www/manager6/Makefile
www/manager6/lxc/Resources.js
www/manager6/qemu/HDReassign.js [new file with mode: 0644]
www/manager6/qemu/HardwareView.js

index e6e01bd1ebd3dc150d47880395a796dafb06eb77..a71015536002d76790dc6e177b21558ba1086f5f 100644 (file)
@@ -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                            \
index 7db55280b8fc7c0c69db5c535778b0bc8186d74b..73defa18cd9ff20088b6d6d78c4459ae4d3b1c5e 100644 (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) {
                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;
            }
@@ -263,7 +302,8 @@ Ext.define('PVE.lxc.RessourceView', {
            var rowdef = rows[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 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 (file)
index 0000000..b6c6796
--- /dev/null
@@ -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();
+    },
+});
index e3506e4d2907ce7d763268fbba98e5412ab179f4..749d355f024e216ba24e61a14ce9d9956efa189d 100644 (file)
@@ -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,