]> git.proxmox.com Git - pve-manager.git/blobdiff - www/manager6/window/Migrate.js
ui: resource tree settings: make wider and clarify field labels
[pve-manager.git] / www / manager6 / window / Migrate.js
index 3a305f32504280889897f45cf929f1ec98426176..1c23abb3e5d296be035655f71c92d9e20718e8a1 100644 (file)
@@ -1,11 +1,10 @@
-/*jslint confusion: true*/
 Ext.define('PVE.window.Migrate', {
     extend: 'Ext.window.Window',
 
-
     vmtype: undefined,
     nodename: undefined,
     vmid: undefined,
+    maxHeight: 450,
 
     viewModel: {
        data: {
@@ -15,25 +14,27 @@ Ext.define('PVE.window.Migrate', {
            running: false,
            qemu: {
                onlineHelp: 'qm_migration',
-               commonName: 'VM'
+               commonName: 'VM',
            },
            lxc: {
                onlineHelp: 'pct_migration',
-               commonName: 'CT'
+               commonName: 'CT',
            },
            migration: {
                possible: true,
                preconditions: [],
                'with-local-disks': 0,
                mode: undefined,
-               allowedNodes: undefined
-           }
+               allowedNodes: undefined,
+               overwriteLocalResourceCheck: false,
+               hasLocalResources: false,
+           },
 
        },
 
        formulas: {
            setMigrationMode: function(get) {
-               if (get('running')){
+               if (get('running')) {
                    if (get('vmtype') === 'qemu') {
                        return gettext('Online');
                    } else {
@@ -49,8 +50,16 @@ Ext.define('PVE.window.Migrate', {
                    } else {
                        return true;
                    }
-           }
-       }
+           },
+           setLocalResourceCheckboxHidden: function(get) {
+               if (get('running') || !get('migration.hasLocalResources') ||
+                   Proxmox.UserName !== 'root@pam') {
+                   return true;
+               } else {
+                   return false;
+               }
+           },
+       },
     },
 
     controller: {
@@ -60,8 +69,8 @@ Ext.define('PVE.window.Migrate', {
                validityChange: function(panel, isValid) {
                    this.getViewModel().set('migration.possible', isValid);
                    this.checkMigratePreconditions();
-               }
-           }
+               },
+           },
        },
 
        init: function(view) {
@@ -83,20 +92,17 @@ Ext.define('PVE.window.Migrate', {
            }
            vm.set('vmtype', view.vmtype);
 
-
            view.setTitle(
-               Ext.String.format('{0} {1}{2}', gettext('Migrate'), vm.get(view.vmtype).commonName, view.vmid)
+               Ext.String.format('{0} {1} {2}', gettext('Migrate'), vm.get(view.vmtype).commonName, view.vmid),
            );
            me.lookup('proxmoxHelpButton').setHelpConfig({
-               onlineHelp: vm.get(view.vmtype).onlineHelp
+               onlineHelp: vm.get(view.vmtype).onlineHelp,
            });
-           me.checkMigratePreconditions();
            me.lookup('formPanel').isValid();
-
        },
 
-       onTargetChange: function (nodeSelector) {
-           //Always display the storages of the currently seleceted migration target
+       onTargetChange: function(nodeSelector) {
+           // Always display the storages of the currently seleceted migration target
            this.lookup('pveDiskStorageSelector').setNodename(nodeSelector.value);
            this.checkMigratePreconditions();
        },
@@ -108,7 +114,7 @@ Ext.define('PVE.window.Migrate', {
 
            var values = me.lookup('formPanel').getValues();
            var params = {
-               target: values.target
+               target: values.target,
            };
 
            if (vm.get('migration.mode')) {
@@ -117,11 +123,16 @@ Ext.define('PVE.window.Migrate', {
            if (vm.get('migration.with-local-disks')) {
                params['with-local-disks'] = 1;
            }
-           //only submit targetstorage if vm is running, storage migration to different storage is only possible online
-           if (vm.get('migration.with-local-disks') && vm.get('running')) {
+           //offline migration to a different storage currently might fail at a late stage
+           //(i.e. after some disks have been moved), so don't expose it yet in the GUI
+           if (vm.get('migration.with-local-disks') && vm.get('running') && values.targetstorage) {
                params.targetstorage = values.targetstorage;
            }
 
+           if (vm.get('migration.overwriteLocalResourceCheck')) {
+               params.force = 1;
+           }
+
            Proxmox.Utils.API2Request({
                params: params,
                url: '/nodes/' + vm.get('nodename') + '/' + vm.get('vmtype') + '/' + vm.get('vmid') + '/migrate',
@@ -136,20 +147,18 @@ Ext.define('PVE.window.Migrate', {
 
                    Ext.create('Proxmox.window.TaskViewer', {
                        upid: upid,
-                       extraTitle: extraTitle
+                       extraTitle: extraTitle,
                    }).show();
 
                    view.close();
-               }
+               },
            });
-
        },
 
-       checkMigratePreconditions: function() {
+       checkMigratePreconditions: function(resetMigrationPossible) {
            var me = this,
                vm = me.getViewModel();
 
-
            var vmrec = PVE.data.ResourceStore.findRecord('vmid', vm.get('vmid'),
                        0, false, false, true);
            if (vmrec && vmrec.data && vmrec.data.running) {
@@ -157,9 +166,9 @@ Ext.define('PVE.window.Migrate', {
            }
 
            if (vm.get('vmtype') === 'qemu') {
-               me.checkQemuPreconditions();
+               me.checkQemuPreconditions(resetMigrationPossible);
            } else {
-               me.checkLxcPreconditions();
+               me.checkLxcPreconditions(resetMigrationPossible);
            }
            me.lookup('pveNodeSelector').disallowedNodes = [vm.get('nodename')];
 
@@ -168,11 +177,10 @@ Ext.define('PVE.window.Migrate', {
            me.lookup('pveNodeSelector').allowedNodes = vm.get('migration.allowedNodes');
 
            me.lookup('formPanel').isValid();
-
        },
 
-       checkQemuPreconditions: function() {
-           var me = this,
+       checkQemuPreconditions: async function(resetMigrationPossible) {
+           let me = this,
                vm = me.getViewModel(),
                migrateStats;
 
@@ -180,90 +188,103 @@ Ext.define('PVE.window.Migrate', {
                vm.set('migration.mode', 'online');
            }
 
-           Proxmox.Utils.API2Request({
-               url: '/nodes/' + vm.get('nodename') + '/' + vm.get('vmtype') + '/' + vm.get('vmid') + '/migrate',
-               method: 'GET',
-               failure: function(response, opts) {
-                   Ext.Msg.alert(gettext('Error'), response.htmlStatus);
-               },
-               success: function(response, options) {
-                   migrateStats = response.result.data;
-                   if (migrateStats.running) {
-                       vm.set('running', true);
-                   }
-                   // Get migration object from viewmodel to prevent
-                   // to many bind callbacks
-                   var migration = vm.get('migration');
-                   migration.preconditions = [];
+           try {
+               if (me.fetchingNodeMigrateInfo && me.fetchingNodeMigrateInfo === vm.get('nodename')) {
+                   return;
+               }
+               me.fetchingNodeMigrateInfo = vm.get('nodename');
+               let { result } = await Proxmox.Async.api2({
+                   url: `/nodes/${vm.get('nodename')}/${vm.get('vmtype')}/${vm.get('vmid')}/migrate`,
+                   method: 'GET',
+               });
+               migrateStats = result.data;
+               me.fetchingNodeMigrateInfo = false;
+           } catch (error) {
+               Ext.Msg.alert(gettext('Error'), error.htmlStatus);
+               return;
+           }
+
+           if (migrateStats.running) {
+               vm.set('running', true);
+           }
+           // Get migration object from viewmodel to prevent to many bind callbacks
+           let migration = vm.get('migration');
+           if (resetMigrationPossible) {
+               migration.possible = true;
+           }
+           migration.preconditions = [];
+
+           if (migrateStats.allowed_nodes) {
+               migration.allowedNodes = migrateStats.allowed_nodes;
+               let target = me.lookup('pveNodeSelector').value;
+               if (target.length && !migrateStats.allowed_nodes.includes(target)) {
+                   let disallowed = migrateStats.not_allowed_nodes[target];
+                   let missingStorages = disallowed.unavailable_storages.join(', ');
+
+                   migration.possible = false;
+                   migration.preconditions.push({
+                       text: 'Storage (' + missingStorages + ') not available on selected target. ' +
+                         'Start VM to use live storage migration or select other target node',
+                       severity: 'error',
+                   });
+               }
+           }
 
-                   if (migrateStats.allowed_nodes) {
-                       migration.allowedNodes = migrateStats.allowed_nodes;
+           if (migrateStats.local_resources.length) {
+               migration.hasLocalResources = true;
+               if (!migration.overwriteLocalResourceCheck || vm.get('running')) {
+                   migration.possible = false;
+                   migration.preconditions.push({
+                       text: Ext.String.format('Can\'t migrate VM with local resources: {0}',
+                       migrateStats.local_resources.join(', ')),
+                       severity: 'error',
+                   });
+               } else {
+                   migration.preconditions.push({
+                       text: Ext.String.format('Migrate VM with local resources: {0}. ' +
+                       'This might fail if resources aren\'t available on the target node.',
+                       migrateStats.local_resources.join(', ')),
+                       severity: 'warning',
+                   });
+               }
+           }
 
-                       if (!migrateStats.allowed_nodes.includes(me.lookup('pveNodeSelector').value)) {
+           if (migrateStats.local_disks.length) {
+               migrateStats.local_disks.forEach(function(disk) {
+                   if (disk.cdrom && disk.cdrom === 1) {
+                       if (!disk.volid.includes('vm-' + vm.get('vmid') + '-cloudinit')) {
                            migration.possible = false;
                            migration.preconditions.push({
-                               text: 'Local storage not available on selected Node, start VM to use live storage migration or select other target node',
-                               icon: '<i class="fa fa-times critical"></i>'
+                               text: "Can't migrate VM with local CD/DVD",
+                               severity: 'error',
                            });
                        }
-                   }
-
-                   if (migrateStats.local_resources.length) {
-                       migration.possible = false;
+                   } else {
+                       let size = disk.size ? '(' + Proxmox.Utils.render_size(disk.size) + ')' : '';
+                       migration['with-local-disks'] = 1;
                        migration.preconditions.push({
-                           text: 'Can\'t migrate VM with local resources: '+ migrateStats.local_resources.join(', '),
-                           icon: '<i class="fa fa-times critical"></i>'
-                       });
-                   }
-
-                   if (migrateStats.local_disks.length) {
-
-                       migrateStats.local_disks.forEach(function (disk) {
-                           if (disk.cdrom && disk.cdrom === 1) {
-                               migration.possible = false;
-                               migration.preconditions.push({
-                                   text:'Can\'t migrate VM with local CD/DVD',
-                                   icon: '<i class="fa fa-times critical"></i>'
-                               });
-
-                           } else if (!disk.referenced_in_config) {
-                               migration.possible = false;
-                               migration.preconditions.push({
-                                   text: 'Found not referenced/unused disk via storage: '+ disk.volid,
-                                   icon: '<i class="fa fa-times critical"></i>'
-                               });
-                           } else {
-                               migration['with-local-disks'] = 1;
-                               migration.preconditions.push({
-                                   text:'Migration with local disk might take long: '+ disk.volid,
-                                   icon: '<i class="fa fa-exclamation-triangle warning"></i>'
-                               });
-                           }
+                           text: Ext.String.format('Migration with local disk might take long: {0} {1}', disk.volid, size),
+                           severity: 'warning',
                        });
-
                    }
+               });
+           }
 
-                   vm.set('migration', migration);
-
-               }
-           });
+           vm.set('migration', migration);
        },
-       checkLxcPreconditions: function() {
-           var me = this,
-               vm = me.getViewModel();
+       checkLxcPreconditions: function(resetMigrationPossible) {
+           let vm = this.getViewModel();
            if (vm.get('running')) {
                vm.set('migration.mode', 'restart');
            }
-       }
-
-
+       },
     },
 
-    width: 700,
+    width: 600,
     modal: true,
     layout: {
        type: 'vbox',
-       align: 'stretch'
+       align: 'stretch',
     },
     border: false,
     items: [
@@ -272,13 +293,31 @@ Ext.define('PVE.window.Migrate', {
            reference: 'formPanel',
            bodyPadding: 10,
            border: false,
-           layout: {
-               type: 'column'
-           },
+           layout: 'hbox',
            items: [
                {
                    xtype: 'container',
-                   columnWidth: 0.5,
+                   flex: 1,
+                   items: [{
+                       xtype: 'displayfield',
+                       name: 'source',
+                       fieldLabel: gettext('Source node'),
+                       bind: {
+                           value: '{nodename}',
+                       },
+                   },
+                   {
+                       xtype: 'displayfield',
+                       reference: 'migrationMode',
+                       fieldLabel: gettext('Mode'),
+                       bind: {
+                           value: '{setMigrationMode}',
+                       },
+                   }],
+               },
+               {
+                   xtype: 'container',
+                   flex: 1,
                    items: [{
                        xtype: 'pveNodeSelector',
                        reference: 'pveNodeSelector',
@@ -288,53 +327,79 @@ Ext.define('PVE.window.Migrate', {
                        disallowedNodes: undefined,
                        onlineValidator: true,
                        listeners: {
-                           change: 'onTargetChange'
-                       }
+                           change: 'onTargetChange',
+                       },
                    },
                    {
-                       xtype: 'displayfield',
-                       reference: 'migrationMode',
-                       fieldLabel: gettext('Mode'),
+                           xtype: 'pveStorageSelector',
+                           reference: 'pveDiskStorageSelector',
+                           name: 'targetstorage',
+                           fieldLabel: gettext('Target storage'),
+                           storageContent: 'images',
+                           allowBlank: true,
+                           autoSelect: false,
+                           emptyText: gettext('Current layout'),
+                           bind: {
+                               hidden: '{setStorageselectorHidden}',
+                           },
+                   },
+                   {
+                       xtype: 'proxmoxcheckbox',
+                       name: 'overwriteLocalResourceCheck',
+                       fieldLabel: gettext('Force'),
+                       autoEl: {
+                           tag: 'div',
+                           'data-qtip': 'Overwrite local resources unavailable check',
+                       },
                        bind: {
-                           value: '{setMigrationMode}'
-                       }
-                   }
-                   ]
+                           hidden: '{setLocalResourceCheckboxHidden}',
+                           value: '{migration.overwriteLocalResourceCheck}',
+                       },
+                       listeners: {
+                           change: {
+                               fn: 'checkMigratePreconditions',
+                               extraArg: true,
+                           },
+                       },
+               }],
                },
-               {
-                   xtype: 'container',
-                   columnWidth: 0.5,
-                   items: [
-                       {
-                               xtype: 'pveStorageSelector',
-                               reference: 'pveDiskStorageSelector',
-                               name: 'targetstorage',
-                               fieldLabel: gettext('Target Storage'),
-                               storageContent: 'images',
-                               bind: {
-                                   hidden: '{setStorageselectorHidden}'
-                               }
-                       }
-                   ]
-               }
-           ]
+           ],
        },
        {
            xtype: 'gridpanel',
            reference: 'preconditionGrid',
+           selectable: false,
            flex: 1,
-           columns: [
-               {text: 'Severity', dataIndex: 'icon', width: 80},
-               {text: 'Info',  dataIndex: 'text', flex: 1}
-           ],
+           columns: [{
+               text: '',
+               dataIndex: 'severity',
+               renderer: function(v) {
+                   switch (v) {
+                       case 'warning':
+                           return '<i class="fa fa-exclamation-triangle warning"></i> ';
+                       case 'error':
+                           return '<i class="fa fa-times critical"></i>';
+                       default:
+                           return v;
+                   }
+               },
+               width: 35,
+           },
+           {
+               text: 'Info',
+               dataIndex: 'text',
+               cellWrap: true,
+               flex: 1,
+           }],
            bind: {
                hidden: '{!migration.preconditions.length}',
                store: {
-                   fields: ['icon','text'],
-                   data: '{migration.preconditions}'
-               }
-           }
-       }
+                   fields: ['severity', 'text'],
+                   data: '{migration.preconditions}',
+                   sorters: 'text',
+               },
+           },
+       },
 
     ],
     buttons: [
@@ -343,7 +408,7 @@ Ext.define('PVE.window.Migrate', {
            reference: 'proxmoxHelpButton',
            onlineHelp: 'pct_migration',
            listenToGlobalEvent: false,
-           hidden: false
+           hidden: false,
        },
        '->',
        {
@@ -352,8 +417,8 @@ Ext.define('PVE.window.Migrate', {
            text: gettext('Migrate'),
            handler: 'startMigration',
            bind: {
-               disabled: '{!migration.possible}'
-           }
-       }
-    ]
-});
\ No newline at end of file
+               disabled: '{!migration.possible}',
+           },
+       },
+    ],
+});