]> git.proxmox.com Git - pve-manager.git/blobdiff - www/manager6/window/GuestImport.js
ui: guest import: add field for scsi controller
[pve-manager.git] / www / manager6 / window / GuestImport.js
index 1a9952d4a188f4d0f957752071e39703844410c8..17c5d8dbbfe81480aec99e7d400ab2a8a4fe8b88 100644 (file)
@@ -84,6 +84,81 @@ Ext.define('PVE.window.GuestImport', {
            }
        },
 
+       calculateConfig: function() {
+           let me = this;
+           let inputPanel = me.lookup('mainInputPanel');
+           let summaryGrid = me.lookup('summaryGrid');
+           let values = inputPanel.getValues();
+           summaryGrid.getStore().setData(Object.entries(values).map(([key, value]) => ({ key, value })));
+       },
+
+       // assume assigned sata disks indices are continuous, so without holes
+       getMaxSata: function() {
+           let me = this;
+           let view = me.getView();
+           if (view.maxSata !== undefined) {
+               return view.maxSata;
+           }
+
+           view.maxSata = -1;
+           for (const key of Object.keys(me.getView().vmConfig)) {
+               if (!key.toLowerCase().startsWith('sata')) {
+                   continue;
+               }
+               let idx = parseInt(key.slice(4), 10);
+               if (idx > view.maxSata) {
+                   view.maxSata = idx;
+               }
+           }
+           me.lookup('diskGrid').getStore().each(rec => {
+               if (!rec.data.id.toLowerCase().startsWith('sata')) {
+                   return;
+               }
+               let idx = parseInt(rec.data.id.slice(4), 10);
+               if (idx > view.maxSata) {
+                   view.maxSata = idx;
+               }
+           });
+           me.lookup('cdGrid').getStore().each(rec => {
+               if (!rec.data.id.toLowerCase().startsWith('sata')) {
+                   return;
+               }
+               let idx = parseInt(rec.data.id.slice(4), 10);
+               if (idx > view.maxSata) {
+                   view.maxSata = idx;
+               }
+           });
+
+           return view.maxSata;
+       },
+
+       mapDisk: function(value, metaData) {
+           let me = this;
+           let mapSata = me.lookup('mapSata');
+           if (mapSata.isDisabled() || !mapSata.getValue()) {
+               return value;
+           }
+           if (!value.toLowerCase().startsWith('scsi')) {
+               return value;
+           }
+           let offset = parseInt(value.slice(4), 10);
+           let newIdx = offset + me.getMaxSata() + 1;
+           if (newIdx > PVE.Utils.diskControllerMaxIDs.sata) {
+               let prefix = '';
+               if (metaData !== undefined) {
+                   // we're in the renderer so put a warning here
+                   let warning = gettext('Too many disks, could not map to SATA.');
+                   prefix = `<i data-qtip="${warning}" class="fa fa-exclamation-triangle warning"></i> `;
+               }
+               return `${prefix}${value}`;
+           }
+           return `sata${newIdx}`;
+       },
+
+       refreshDiskGrid: function() {
+           this.lookup('diskGrid').reconfigure();
+       },
+
        control: {
            'grid field': {
                // update records from widgetcolumns
@@ -102,6 +177,15 @@ Ext.define('PVE.window.GuestImport', {
            'field[name=osbase]': {
                change: 'onOSBaseChange',
            },
+           'panel[reference=summaryTab]': {
+               activate: 'calculateConfig',
+           },
+           'proxmoxcheckbox[reference=mapSata]': {
+               change: 'refreshDiskGrid',
+           },
+           'combobox[name=ostype]': {
+               change: 'refreshDiskGrid',
+           },
        },
     },
 
@@ -109,6 +193,8 @@ Ext.define('PVE.window.GuestImport', {
        data: {
            coreCount: 1,
            socketCount: 1,
+           liveImport: false,
+           os: '',
            warnings: [],
        },
 
@@ -117,6 +203,9 @@ Ext.define('PVE.window.GuestImport', {
            hideWarnings: get => get('warnings').length === 0,
            warningsText: get => '<ul style="margin: 0; padding-left: 20px;">'
                + get('warnings').map(w => `<li>${w}</li>`).join('') + '</ul>',
+           liveImportNote: get => !get('liveImport') ? ''
+               : gettext('Note: If anything goes wrong during the live-import, new data written by the VM may be lost.'),
+           isWindows: get => (get('os') ?? '').startsWith('win'),
        },
     },
 
@@ -153,16 +242,28 @@ Ext.define('PVE.window.GuestImport', {
                            config.scsi0 = config.scsi0.replace('local:0,', 'local:0,format=qcow2,');
                        }
 
+                       let parsedBoot = PVE.Parser.parsePropertyString(config.boot ?? '');
+                       if (parsedBoot.order) {
+                           parsedBoot.order = parsedBoot.order.split(';');
+                       }
+
                        grid.lookup('diskGrid').getStore().each((rec) => {
                            if (!rec.data.enable) {
                                return;
                            }
-                           let id = rec.data.id;
+                           let id = grid.getController().mapDisk(rec.data.id);
+                           if (id !== rec.data.id && parsedBoot?.order) {
+                               let idx = parsedBoot.order.indexOf(rec.data.id);
+                               if (idx !== -1) {
+                                   parsedBoot.order[idx] = id;
+                               }
+                           }
                            let data = {
                                ...rec.data,
                            };
                            delete data.enable;
                            delete data.id;
+                           delete data.size;
                            if (!data.file) {
                                data.file = defaultStorage;
                                data.format = defaultFormat;
@@ -174,6 +275,11 @@ Ext.define('PVE.window.GuestImport', {
                            config[id] = PVE.Parser.printQemuDrive(data);
                        });
 
+                       if (parsedBoot.order) {
+                           parsedBoot.order = parsedBoot.order.join(';');
+                       }
+                       config.boot = PVE.Parser.printPropertyString(parsedBoot);
+
                        grid.lookup('netGrid').getStore().each((rec) => {
                            if (!rec.data.enable) {
                                return;
@@ -202,10 +308,19 @@ Ext.define('PVE.window.GuestImport', {
                            config[id] = PVE.Parser.printPropertyString(cd);
                        });
 
+                       config.scsihw = grid.lookup('scsihw').getValue();
+
                        if (grid.lookup('liveimport').getValue()) {
                            config['live-restore'] = 1;
                        }
 
+                       // remove __default__ values
+                       for (const [key, value] of Object.entries(config)) {
+                           if (value === '__default__') {
+                               delete config[key];
+                           }
+                       }
+
                        return config;
                    },
 
@@ -312,6 +427,9 @@ Ext.define('PVE.window.GuestImport', {
                            queryMode: 'local',
                            valueField: 'val',
                            displayField: 'desc',
+                           bind: {
+                               value: '{os}',
+                           },
                            store: {
                                fields: ['desc', 'val'],
                                data: PVE.Utils.kvm_ostypes.Linux,
@@ -327,6 +445,17 @@ Ext.define('PVE.window.GuestImport', {
                    ],
 
                    columnB: [
+                       {
+                           xtype: 'proxmoxcheckbox',
+                           fieldLabel: gettext('Live Import'),
+                           reference: 'liveimport',
+                           isFormField: false,
+                           boxLabelCls: 'pmx-hint black x-form-cb-label',
+                           bind: {
+                               value: '{liveImport}',
+                               boxLabel: '{liveImportNote}',
+                           },
+                       },
                        {
                            xtype: 'displayfield',
                            fieldLabel: gettext('Warnings'),
@@ -351,7 +480,38 @@ Ext.define('PVE.window.GuestImport', {
                {
                    title: gettext('Advanced'),
                    xtype: 'inputpanel',
-                   items: [
+
+                   column1: [
+                       {
+                           xtype: 'pveScsiHwSelector',
+                           reference: 'scsihw',
+                           name: 'scsihw',
+                           submitValue: false,
+                           fieldLabel: gettext('SCSI Controller'),
+                       },
+                   ],
+
+                   column2: [
+                       {
+                           xtype: 'proxmoxcheckbox',
+                           fieldLabel: gettext('Map SCSI to SATA'),
+                           labelWidth: 120,
+                           reference: 'mapSata',
+                           isFormField: false,
+                           hidden: true,
+                           disabled: true,
+                           bind: {
+                               hidden: '{!isWindows}',
+                               disabled: '{!isWindows}',
+                           },
+                           autoEl: {
+                               tag: 'div',
+                               'data-qtip': gettext('Useful when wanting to use VirtIO-SCSI'),
+                           },
+                       },
+                   ],
+
+                   columnB: [
                        {
                            xtype: 'displayfield',
                            fieldLabel: gettext('Disks'),
@@ -360,7 +520,7 @@ Ext.define('PVE.window.GuestImport', {
                        {
                            xtype: 'grid',
                            reference: 'diskGrid',
-                           minHeight: 58,
+                           minHeight: 60,
                            maxHeight: 150,
                            store: {
                                data: [],
@@ -383,6 +543,7 @@ Ext.define('PVE.window.GuestImport', {
                                {
                                    text: gettext('Disk'),
                                    dataIndex: 'id',
+                                   renderer: 'mapDisk',
                                },
                                {
                                    text: gettext('Source'),
@@ -392,6 +553,16 @@ Ext.define('PVE.window.GuestImport', {
                                        return value.replace(/^.*\//, '');
                                    },
                                },
+                               {
+                                   text: gettext('Size'),
+                                   dataIndex: 'size',
+                                   renderer: (value) => {
+                                       if (Ext.isNumeric(value)) {
+                                           return Proxmox.Utils.render_size(value);
+                                       }
+                                       return value ?? Proxmox.Utils.unknownText;
+                                   },
+                               },
                                {
                                    text: gettext('Storage'),
                                    dataIndex: 'file',
@@ -427,11 +598,14 @@ Ext.define('PVE.window.GuestImport', {
                            xtype: 'displayfield',
                            fieldLabel: gettext('CD/DVD Drives'),
                            labelWidth: 200,
+                           style: {
+                               paddingTop: '10px',
+                           },
                        },
                        {
                            xtype: 'grid',
                            reference: 'cdGrid',
-                           minHeight: 58,
+                           minHeight: 60,
                            maxHeight: 150,
                            store: {
                                data: [],
@@ -491,6 +665,9 @@ Ext.define('PVE.window.GuestImport', {
                            xtype: 'displayfield',
                            fieldLabel: gettext('Network Interfaces'),
                            labelWidth: 200,
+                           style: {
+                               paddingTop: '10px',
+                           },
                        },
                        {
                            xtype: 'grid',
@@ -556,6 +733,29 @@ Ext.define('PVE.window.GuestImport', {
                        },
                    ],
                },
+               {
+                   title: gettext('Resulting Config'),
+                   reference: 'summaryTab',
+                   items: [
+                       {
+                           xtype: 'grid',
+                           reference: 'summaryGrid',
+                           maxHeight: 400,
+                           scrollable: true,
+                           store: {
+                               model: 'KeyValue',
+                               sorters: [{
+                                   property: 'key',
+                                   direction: 'ASC',
+                               }],
+                           },
+                           columns: [
+                               { header: 'Key', width: 150, dataIndex: 'key' },
+                               { header: 'Value', flex: 1, dataIndex: 'value' },
+                           ],
+                       },
+                   ],
+               },
            ],
        },
     ],
@@ -577,13 +777,6 @@ Ext.define('PVE.window.GuestImport', {
 
        me.callParent();
 
-       me.query('toolbar')?.[0]?.insert(0, {
-           xtype: 'proxmoxcheckbox',
-           reference: 'liveimport',
-           boxLabelAlign: 'before',
-           boxLabel: gettext('Live Import'),
-       });
-
        me.setTitle(Ext.String.format(gettext('Import Guest - {0}'), `${me.storage}:${me.volumeName}`));
 
        me.lookup('defaultStorage').setNodename(me.nodename);
@@ -591,10 +784,11 @@ Ext.define('PVE.window.GuestImport', {
 
        let renderWarning = w => {
            const warningsCatalogue = {
-               'cdrom-image-ignored': gettext("CD-ROM images cannot get imported, please reconfigure the '{0}' drive after the import"),
+               'cdrom-image-ignored': gettext("CD-ROM images cannot get imported, if required you can reconfigure the '{0}' drive in the 'Advanced' tab."),
                'nvme-unsupported': gettext("NVMe disks are currently not supported, '{0}' will get attaced as SCSI"),
                'ovmf-with-lsi-unsupported': gettext("OVMF is built without LSI drivers, scsi hardware was set to '{1}'"),
                'serial-port-socket-only': gettext("Serial socket '{0}' will be mapped to a socket"),
+               'guest-is-running': gettext('Virtual guest seems to be running on source host. Import might fail or have inconsistent state!'),
            };
             let message = warningsCatalogue[w.type];
            if (!w.type || !message) {
@@ -610,10 +804,17 @@ Ext.define('PVE.window.GuestImport', {
 
                let disks = [];
                for (const [id, value] of Object.entries(data.disks ?? {})) {
+                   let volid = Ext.htmlEncode('<none>');
+                   let size = 'auto';
+                   if (Ext.isObject(value)) {
+                       volid = value.volid;
+                       size = value.size;
+                   }
                    disks.push({
                        id,
                        enable: true,
-                       'import-from': id === 'efidisk0' ? Ext.htmlEncode('<none>') : value,
+                       size,
+                       'import-from': volid,
                        format: 'raw',
                    });
                }