X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;ds=sidebyside;f=www%2Fmanager6%2Fwindow%2FGuestImport.js;h=7b6dce6c227595535099b4fb55a2d135f62d72cd;hb=c8c53aa41300267f6bfa2443fe261b8269193f26;hp=7b0105e61349e89dbadb8a6be5b932a651628343;hpb=d39e2492de3f96b1c391f88f6414068967b1f832;p=pve-manager.git diff --git a/www/manager6/window/GuestImport.js b/www/manager6/window/GuestImport.js index 7b0105e6..7b6dce6c 100644 --- a/www/manager6/window/GuestImport.js +++ b/www/manager6/window/GuestImport.js @@ -92,58 +92,87 @@ Ext.define('PVE.window.GuestImport', { summaryGrid.getStore().setData(Object.entries(values).map(([key, value]) => ({ key, value }))); }, + calculateAdditionalCDIdx: function() { + let me = this; + + let maxIde = me.getMaxControllerId('ide'); + let maxSata = me.getMaxControllerId('sata'); + // only ide0 and ide2 can be used reliably for isos (e.g. for q35) + if (maxIde < 0) { + return 'ide0'; + } + if (maxIde < 2) { + return 'ide2'; + } + if (maxSata < PVE.Utils.diskControllerMaxIDs.sata - 1) { + return `sata${maxSata+1}`; + } + + return ''; + }, + // assume assigned sata disks indices are continuous, so without holes - getMaxSata: function() { + getMaxControllerId: function(controller) { let me = this; let view = me.getView(); - if (view.maxSata !== undefined) { - return view.maxSata; + if (!controller) { + return -1; + } + + let max = view[`max${controller}`]; + if (max !== undefined) { + return max; } - view.maxSata = -1; + max = -1; for (const key of Object.keys(me.getView().vmConfig)) { - if (!key.toLowerCase().startsWith('sata')) { + if (!key.toLowerCase().startsWith(controller)) { continue; } - let idx = parseInt(key.slice(4), 10); - if (idx > view.maxSata) { - view.maxSata = idx; + let idx = parseInt(key.slice(controller.length), 10); + if (idx > max) { + max = idx; } } me.lookup('diskGrid').getStore().each(rec => { - if (!rec.data.id.toLowerCase().startsWith('sata')) { + if (!rec.data.id.toLowerCase().startsWith(controller)) { return; } - let idx = parseInt(rec.data.id.slice(4), 10); - if (idx > view.maxSata) { - view.maxSata = idx; + let idx = parseInt(rec.data.id.slice(controller.length), 10); + if (idx > max) { + max = idx; } }); me.lookup('cdGrid').getStore().each(rec => { - if (!rec.data.id.toLowerCase().startsWith('sata')) { + if (!rec.data.id.toLowerCase().startsWith(controller) || rec.data.hidden) { return; } - let idx = parseInt(rec.data.id.slice(4), 10); - if (idx > view.maxSata) { - view.maxSata = idx; + let idx = parseInt(rec.data.id.slice(controller.length), 10); + if (idx > max) { + max = idx; } }); - return view.maxSata; + view[`max${controller}`] = max; + return max; }, mapDisk: function(value, metaData) { let me = this; - let mapSata = me.lookup('mapSata'); - if (mapSata.isDisabled() || !mapSata.getValue()) { + let prepareForVirtIO = me.lookup('prepareForVirtIO'); + if (prepareForVirtIO.isDisabled() || !prepareForVirtIO.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 newIdx = offset + me.getMaxControllerId('sata') + 1; + if (me.getViewModel().get('isWindows') && me.getView().additionalCdIdx?.startsWith('sata')) { + // additionalCdIdx takes the highest sata port + newIdx++; + } + if (newIdx >= PVE.Utils.diskControllerMaxIDs.sata) { let prefix = ''; if (metaData !== undefined) { // we're in the renderer so put a warning here @@ -155,8 +184,62 @@ Ext.define('PVE.window.GuestImport', { return `sata${newIdx}`; }, - refreshDiskGrid: function() { + refreshGrids: function() { this.lookup('diskGrid').reconfigure(); + this.lookup('cdGrid').reconfigure(); + this.lookup('netGrid').reconfigure(); + }, + + onOSTypeChange: function(_cb, value) { + let me = this; + if (!value) { + return; + } + let store = me.lookup('cdGrid').getStore(); + let collection = store.getData().getSource() ?? store.getData(); + let rec = collection.find('autogenerated', true); + + let isWindows = (value ?? '').startsWith('w'); + if (rec) { + rec.set('hidden', !isWindows); + rec.commit(); + } + let prepareVirtio = me.lookup('prepareForVirtIO').getValue(); + let defaultScsiHw = me.getView().vmConfig.scsihw ?? '__default__'; + me.lookup('scsihw').setValue(prepareVirtio && isWindows ? 'virtio-scsi-single' : defaultScsiHw); + + me.refreshGrids(); + }, + + onPrepareVirtioChange: function(_cb, value) { + let me = this; + + let scsihw = me.lookup('scsihw'); + scsihw.suspendEvents(); + scsihw.setValue(value ? 'virtio-scsi-single' : me.getView().vmConfig.scsihw); + scsihw.resumeEvents(); + + me.refreshGrids(); + }, + + onScsiHwChange: function(_field, value) { + let me = this; + me.getView().vmConfig.scsihw = value; + }, + + onUniqueMACChange: function(_cb, value) { + let me = this; + + me.getViewModel().set('uniqueMACAdresses', value); + + me.lookup('netGrid').reconfigure(); + }, + + renderMacAddress: function(value, metaData, record, rowIndex, colIndex, store, view) { + let me = this; + let vm = me.getViewModel(); + + return !vm.get('uniqueMACAdresses') && value ? value : 'auto'; }, control: { @@ -180,11 +263,17 @@ Ext.define('PVE.window.GuestImport', { 'panel[reference=summaryTab]': { activate: 'calculateConfig', }, - 'proxmoxcheckbox[reference=mapSata]': { - change: 'refreshDiskGrid', + 'proxmoxcheckbox[reference=prepareForVirtIO]': { + change: 'onPrepareVirtioChange', }, 'combobox[name=ostype]': { - change: 'refreshDiskGrid', + change: 'onOSTypeChange', + }, + 'pveScsiHwSelector': { + change: 'onScsiHwChange', + }, + 'proxmoxcheckbox[name=uniqueMACs]': { + change: 'onUniqueMACChange', }, }, }, @@ -194,7 +283,9 @@ Ext.define('PVE.window.GuestImport', { coreCount: 1, socketCount: 1, liveImport: false, - os: '', + os: 'l26', + maxCdDrives: false, + uniqueMACAdresses: false, warnings: [], }, @@ -205,541 +296,576 @@ Ext.define('PVE.window.GuestImport', { + get('warnings').map(w => `
  • ${w}
  • `).join('') + '', 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'), + isWindows: get => (get('os') ?? '').startsWith('w'), }, }, width: 700, bodyPadding: 0, - items: [ - { - xtype: 'tabpanel', - defaults: { - bodyPadding: 10, - }, - items: [ - { - title: gettext('General'), - xtype: 'inputpanel', - reference: 'mainInputPanel', - onGetValues: function(values) { - let me = this; - let grid = me.up('pveGuestImportWindow'); - - // from pveDiskStorageSelector - let defaultStorage = values.hdstorage; - let defaultFormat = values.diskformat; - delete values.hdstorage; - delete values.diskformat; - - let defaultBridge = values.defaultBridge; - delete values.defaultBridge; - - let config = Ext.apply(grid.vmConfig, values); - - if (config.scsi0) { - config.scsi0 = config.scsi0.replace('local:0,', 'local:0,format=qcow2,'); - } + items: [{ + xtype: 'tabpanel', + defaults: { + bodyPadding: 10, + }, + items: [ + { + title: gettext('General'), + xtype: 'inputpanel', + reference: 'mainInputPanel', + onGetValues: function(values) { + let me = this; + let grid = me.up('pveGuestImportWindow'); + let vm = grid.getViewModel(); + + // from pveDiskStorageSelector + let defaultStorage = values.hdstorage; + let defaultFormat = values.diskformat; + delete values.hdstorage; + delete values.diskformat; + + let defaultBridge = values.defaultBridge; + delete values.defaultBridge; + + let config = Ext.apply(grid.vmConfig, values); + + if (config.scsi0) { + 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(';'); - } + 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 = 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; - } - data.file += ':0'; // for our special api format - if (id === 'efidisk0') { - delete data['import-from']; + grid.lookup('diskGrid').getStore().each((rec) => { + if (!rec.data.enable) { + return; + } + 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; } - config[id] = PVE.Parser.printQemuDrive(data); - }); + } + let data = { + ...rec.data, + }; + delete data.enable; + delete data.id; + delete data.size; + if (!data.file) { + data.file = defaultStorage; + data.format = defaultFormat; + } + data.file += ':0'; // for our special api format + if (id === 'efidisk0') { + delete data['import-from']; + } + config[id] = PVE.Parser.printQemuDrive(data); + }); - if (parsedBoot.order) { - parsedBoot.order = parsedBoot.order.join(';'); + 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; + } + let id = rec.data.id; + let data = { + ...rec.data, + }; + delete data.enable; + delete data.id; + if (!data.bridge) { + data.bridge = defaultBridge; } - config.boot = PVE.Parser.printPropertyString(parsedBoot); + if (vm.get('uniqueMACAdresses')) { + data.macaddr = undefined; + } + config[id] = PVE.Parser.printQemuNetwork(data); + }); - grid.lookup('netGrid').getStore().each((rec) => { - if (!rec.data.enable) { - return; - } - let id = rec.data.id; - let data = { - ...rec.data, - }; - delete data.enable; - delete data.id; - if (!data.bridge) { - data.bridge = defaultBridge; - } - config[id] = PVE.Parser.printQemuNetwork(data); - }); + grid.lookup('cdGrid').getStore().each((rec) => { + if (!rec.data.enable) { + return; + } + let id = rec.data.id; + let cd = { + media: 'cdrom', + file: rec.data.file ? rec.data.file : 'none', + }; + config[id] = PVE.Parser.printPropertyString(cd); + }); - grid.lookup('cdGrid').getStore().each((rec) => { - if (!rec.data.enable) { - return; - } - let id = rec.data.id; - let cd = { - media: 'cdrom', - file: rec.data.file ? rec.data.file : 'none', - }; - config[id] = PVE.Parser.printPropertyString(cd); - }); - - if (grid.lookup('liveimport').getValue()) { - config['live-restore'] = 1; + 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; - }, + return config; + }, - column1: [ - { - xtype: 'pveGuestIDSelector', - name: 'vmid', - fieldLabel: 'VM', - guestType: 'qemu', - loadNextFreeID: true, - }, - { - xtype: 'proxmoxintegerfield', - fieldLabel: gettext('Sockets'), - name: 'sockets', - reference: 'socketsField', - value: 1, - minValue: 1, - maxValue: 4, - allowBlank: true, - bind: { - value: '{socketCount}', - }, + column1: [ + { + xtype: 'pveGuestIDSelector', + name: 'vmid', + fieldLabel: 'VM', + guestType: 'qemu', + loadNextFreeID: true, + }, + { + xtype: 'proxmoxintegerfield', + fieldLabel: gettext('Sockets'), + name: 'sockets', + reference: 'socketsField', + value: 1, + minValue: 1, + maxValue: 128, + allowBlank: true, + bind: { + value: '{socketCount}', }, - { - xtype: 'proxmoxintegerfield', - fieldLabel: gettext('Cores'), - name: 'cores', - reference: 'coresField', - value: 1, - minValue: 1, - maxValue: 128, - allowBlank: true, - bind: { - value: '{coreCount}', - }, + }, + { + xtype: 'proxmoxintegerfield', + fieldLabel: gettext('Cores'), + name: 'cores', + reference: 'coresField', + value: 1, + minValue: 1, + maxValue: 1024, + allowBlank: true, + bind: { + value: '{coreCount}', }, - { - xtype: 'pveMemoryField', - fieldLabel: gettext('Memory'), - name: 'memory', - reference: 'memoryField', - value: 512, - allowBlank: true, + }, + { + xtype: 'pveMemoryField', + fieldLabel: gettext('Memory') + ' (MiB)', + name: 'memory', + reference: 'memoryField', + value: 512, + allowBlank: true, + }, + { xtype: 'displayfield' }, // spacer + { xtype: 'displayfield' }, // spacer + { + xtype: 'pveDiskStorageSelector', + reference: 'defaultStorage', + storageLabel: gettext('Default Storage'), + storageContent: 'images', + autoSelect: true, + hideSize: true, + name: 'defaultStorage', + }, + ], + + column2: [ + { + xtype: 'textfield', + fieldLabel: gettext('Name'), + name: 'name', + vtype: 'DnsName', + reference: 'nameField', + allowBlank: true, + }, + { + xtype: 'CPUModelSelector', + name: 'cpu', + reference: 'cputype', + value: 'x86-64-v2-AES', + fieldLabel: gettext('CPU Type'), + }, + { + xtype: 'displayfield', + fieldLabel: gettext('Total cores'), + name: 'totalcores', + isFormField: false, + bind: { + value: '{totalCoreCount}', }, - { - //spacer - xtype: 'displayfield', + }, + { + xtype: 'combobox', + submitValue: false, + name: 'osbase', + fieldLabel: gettext('OS Type'), + editable: false, + queryMode: 'local', + value: 'Linux', + store: Object.keys(PVE.Utils.kvm_ostypes), + }, + { + xtype: 'combobox', + name: 'ostype', + reference: 'ostype', + fieldLabel: gettext('Version'), + value: 'l26', + allowBlank: false, + editable: false, + queryMode: 'local', + valueField: 'val', + displayField: 'desc', + bind: { + value: '{os}', }, - { - xtype: 'pveDiskStorageSelector', - reference: 'defaultStorage', - storageLabel: gettext('Default Storage'), - storageContent: 'images', - autoSelect: true, - hideSize: true, - name: 'defaultStorage', + store: { + fields: ['desc', 'val'], + data: PVE.Utils.kvm_ostypes.Linux, }, - ], - - column2: [ - { - xtype: 'textfield', - fieldLabel: gettext('Name'), - name: 'name', - vtype: 'DnsName', - reference: 'nameField', - allowBlank: true, + }, + { xtype: 'displayfield' }, // spacer + { + xtype: 'PVE.form.BridgeSelector', + reference: 'defaultBridge', + name: 'defaultBridge', + allowBlank: false, + fieldLabel: gettext('Default Bridge'), + }, + ], + + 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: 'CPUModelSelector', - name: 'cpu', - reference: 'cputype', - value: 'x86-64-v2-AES', - fieldLabel: gettext('Type'), + }, + { + xtype: 'displayfield', + fieldLabel: gettext('Warnings'), + labelWidth: 200, + hidden: true, + bind: { + hidden: '{hideWarnings}', }, - { - xtype: 'displayfield', - fieldLabel: gettext('Total cores'), - name: 'totalcores', - isFormField: false, - bind: { - value: '{totalCoreCount}', - }, + }, + { + xtype: 'displayfield', + reference: 'warningText', + userCls: 'pmx-hint', + hidden: true, + bind: { + hidden: '{hideWarnings}', + value: '{warningsText}', }, - { - xtype: 'combobox', - submitValue: false, - name: 'osbase', - fieldLabel: gettext('OS Type'), - editable: false, - queryMode: 'local', - value: 'Linux', - store: Object.keys(PVE.Utils.kvm_ostypes), + }, + ], + }, + { + title: gettext('Advanced'), + xtype: 'inputpanel', + + // the first inputpanel handles all values, so prevent value leakage here + onGetValues: () => ({}), + + columnT: [ + { + xtype: 'displayfield', + fieldLabel: gettext('Disks'), + labelWidth: 200, + }, + { + xtype: 'grid', + reference: 'diskGrid', + minHeight: 60, + maxHeight: 150, + store: { + data: [], + sorters: [ + 'id', + ], }, - { - xtype: 'combobox', - name: 'ostype', - reference: 'ostype', - fieldLabel: gettext('Version'), - value: 'l26', - allowBlank: false, - editable: false, - queryMode: 'local', - valueField: 'val', - displayField: 'desc', - bind: { - value: '{os}', - }, - store: { - fields: ['desc', 'val'], - data: PVE.Utils.kvm_ostypes.Linux, + columns: [ + { + xtype: 'checkcolumn', + header: gettext('Use'), + width: 50, + dataIndex: 'enable', + listeners: { + checkchange: function(_column, _rowIndex, _checked, record) { + record.commit(); + }, + }, }, - }, - { - xtype: 'PVE.form.BridgeSelector', - reference: 'defaultBridge', - name: 'defaultBridge', - allowBlank: false, - fieldLabel: gettext('Default Bridge'), - }, - ], - - columnB: [ - { - xtype: 'proxmoxcheckbox', - fieldLabel: gettext('Live Import'), - reference: 'liveimport', - isFormField: false, - boxLabelCls: 'pmx-hint black x-form-cb-label', - bind: { - value: '{liveImport}', - boxLabel: '{liveImportNote}', + { + text: gettext('Disk'), + dataIndex: 'id', + renderer: 'mapDisk', }, - }, - { - xtype: 'displayfield', - fieldLabel: gettext('Warnings'), - labelWidth: 200, - hidden: true, - bind: { - hidden: '{hideWarnings}', + { + text: gettext('Source'), + dataIndex: 'import-from', + flex: 1, + renderer: function(value) { + return value.replace(/^.*\//, ''); + }, }, - }, - { - xtype: 'displayfield', - reference: 'warningText', - userCls: 'pmx-hint', - hidden: true, - bind: { - hidden: '{hideWarnings}', - value: '{warningsText}', + { + text: gettext('Size'), + dataIndex: 'size', + renderer: (value) => { + if (Ext.isNumeric(value)) { + return Proxmox.Utils.render_size(value); + } + return value ?? Proxmox.Utils.unknownText; + }, }, - }, - ], - }, - { - title: gettext('Advanced'), - xtype: 'inputpanel', - - column1: [ - { - xtype: 'proxmoxcheckbox', - fieldLabel: gettext('Map SCSI to SATA'), - labelWidth: 120, - reference: 'mapSata', - isFormField: false, - hidden: true, - disabled: true, - bind: { - hidden: '{!isWindows}', - disabled: '{!isWindows}', + { + text: gettext('Storage'), + dataIndex: 'file', + xtype: 'widgetcolumn', + width: 150, + widget: { + xtype: 'pveStorageSelector', + isFormField: false, + autoSelect: false, + allowBlank: true, + emptyText: gettext('From Default'), + name: 'file', + storageContent: 'images', + }, + onWidgetAttach: 'setNodename', }, - autoEl: { - tag: 'div', - 'data-qtip': gettext('Useful when wanting to use VirtIO-SCSI'), + { + text: gettext('Format'), + dataIndex: 'format', + xtype: 'widgetcolumn', + width: 150, + widget: { + xtype: 'pveDiskFormatSelector', + name: 'format', + disabled: true, + isFormField: false, + matchFieldWidth: false, + }, }, + ], + }, + ], + + column1: [ + { + xtype: 'proxmoxcheckbox', + fieldLabel: gettext('Prepare for VirtIO-SCSI'), + labelWidth: 200, + reference: 'prepareForVirtIO', + name: 'prepareForVirtIO', + submitValue: false, + disabled: true, + bind: { + disabled: '{!isWindows}', }, - ], - - columnB: [ - { - xtype: 'displayfield', - fieldLabel: gettext('Disks'), - labelWidth: 200, + autoEl: { + tag: 'div', + 'data-qtip': gettext('Maps SCSI disks to SATA and changes the SCSI Controller. Useful for a quicker switch to VirtIO-SCSI attached disks'), }, - { - xtype: 'grid', - reference: 'diskGrid', - minHeight: 60, - maxHeight: 150, - store: { - data: [], - sorters: [ - 'id', - ], - }, - columns: [ - { - xtype: 'checkcolumn', - header: gettext('Use'), - width: 50, - dataIndex: 'enable', - listeners: { - checkchange: function(_column, _rowIndex, _checked, record) { - record.commit(); - }, - }, - }, - { - text: gettext('Disk'), - dataIndex: 'id', - renderer: 'mapDisk', - }, - { - text: gettext('Source'), - dataIndex: 'import-from', - flex: 1, - renderer: function(value) { - 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', - xtype: 'widgetcolumn', - width: 150, - widget: { - xtype: 'pveStorageSelector', - isFormField: false, - autoSelect: false, - allowBlank: true, - emptyText: gettext('From Default'), - name: 'file', - storageContent: 'images', - }, - onWidgetAttach: 'setNodename', - }, - { - text: gettext('Format'), - dataIndex: 'format', - xtype: 'widgetcolumn', - width: 150, - widget: { - xtype: 'pveDiskFormatSelector', - name: 'format', - disabled: true, - isFormField: false, - matchFieldWidth: false, - }, + }, + ], + + column2: [ + { + xtype: 'pveScsiHwSelector', + reference: 'scsihw', + name: 'scsihw', + value: '__default__', + submitValue: false, + fieldLabel: gettext('SCSI Controller'), + }, + ], + + columnB: [ + { + xtype: 'displayfield', + fieldLabel: gettext('CD/DVD Drives'), + labelWidth: 200, + }, + { + xtype: 'grid', + reference: 'cdGrid', + minHeight: 60, + maxHeight: 150, + store: { + data: [], + sorters: [ + 'id', + ], + filters: [ + function(rec) { + return !rec.data.hidden; }, ], }, - { - xtype: 'displayfield', - fieldLabel: gettext('CD/DVD Drives'), - labelWidth: 200, - style: { - paddingTop: '10px', - }, - }, - { - xtype: 'grid', - reference: 'cdGrid', - minHeight: 60, - maxHeight: 150, - store: { - data: [], - sorters: [ - 'id', - ], - }, - columns: [ - { - xtype: 'checkcolumn', - header: gettext('Use'), - width: 50, - dataIndex: 'enable', - listeners: { - checkchange: function(_column, _rowIndex, _checked, record) { - record.commit(); - }, + columns: [ + { + xtype: 'checkcolumn', + header: gettext('Use'), + width: 50, + dataIndex: 'enable', + listeners: { + checkchange: function(_column, _rowIndex, _checked, record) { + record.commit(); }, }, - { - text: gettext('Slot'), - dataIndex: 'id', - sorted: true, - }, - { - text: gettext('Storage'), - xtype: 'widgetcolumn', - width: 150, - widget: { - xtype: 'pveStorageSelector', - isFormField: false, - autoSelect: false, - allowBlank: true, - emptyText: Proxmox.Utils.noneText, - storageContent: 'iso', - }, - onWidgetAttach: 'setNodename', + }, + { + text: gettext('Slot'), + dataIndex: 'id', + sorted: true, + }, + { + text: gettext('Storage'), + xtype: 'widgetcolumn', + width: 150, + widget: { + xtype: 'pveStorageSelector', + isFormField: false, + autoSelect: false, + allowBlank: true, + emptyText: Proxmox.Utils.noneText, + storageContent: 'iso', }, - { - text: gettext('ISO'), - dataIndex: 'file', - xtype: 'widgetcolumn', - flex: 1, - widget: { - xtype: 'pveFileSelector', - name: 'file', - isFormField: false, - allowBlank: true, - emptyText: Proxmox.Utils.noneText, - storageContent: 'iso', - }, - onWidgetAttach: 'setNodename', + onWidgetAttach: 'setNodename', + }, + { + text: gettext('ISO'), + dataIndex: 'file', + xtype: 'widgetcolumn', + flex: 1, + widget: { + xtype: 'pveFileSelector', + name: 'file', + isFormField: false, + allowBlank: true, + emptyText: Proxmox.Utils.noneText, + storageContent: 'iso', }, - ], - }, - { - xtype: 'displayfield', - fieldLabel: gettext('Network Interfaces'), - labelWidth: 200, - style: { - paddingTop: '10px', + onWidgetAttach: 'setNodename', }, + ], + }, + { + xtype: 'displayfield', + fieldLabel: gettext('Network Interfaces'), + labelWidth: 200, + style: { + paddingTop: '10px', }, - { - xtype: 'grid', - minHeight: 58, - maxHeight: 150, - reference: 'netGrid', - store: { - data: [], - sorters: [ - 'id', - ], - }, - columns: [ - { - xtype: 'checkcolumn', - header: gettext('Use'), - width: 50, - dataIndex: 'enable', - listeners: { - checkchange: function(_column, _rowIndex, _checked, record) { - record.commit(); - }, + }, + { + xtype: 'grid', + minHeight: 58, + maxHeight: 150, + reference: 'netGrid', + store: { + data: [], + sorters: [ + 'id', + ], + }, + columns: [ + { + xtype: 'checkcolumn', + header: gettext('Use'), + width: 50, + dataIndex: 'enable', + listeners: { + checkchange: function(_column, _rowIndex, _checked, record) { + record.commit(); }, }, - { - text: gettext('ID'), - dataIndex: 'id', - }, - { - text: gettext('MAC address'), - flex: 1, - dataIndex: 'macaddr', - renderer: value => value ?? 'auto', - }, - { - text: gettext('Model'), - flex: 1, - dataIndex: 'model', - xtype: 'widgetcolumn', - widget: { - xtype: 'pveNetworkCardSelector', - name: 'model', - isFormField: false, - allowBlank: false, - }, + }, + { + text: gettext('ID'), + dataIndex: 'id', + }, + { + text: gettext('MAC address'), + flex: 1, + dataIndex: 'macaddr', + renderer: 'renderMacAddress', + }, + { + text: gettext('Model'), + flex: 1, + dataIndex: 'model', + xtype: 'widgetcolumn', + widget: { + xtype: 'pveNetworkCardSelector', + name: 'model', + isFormField: false, + allowBlank: false, }, - { - text: gettext('Bridge'), - dataIndex: 'bridge', - xtype: 'widgetcolumn', - flex: 1, - widget: { - xtype: 'PVE.form.BridgeSelector', - name: 'bridge', - isFormField: false, - autoSelect: false, - allowBlank: true, - emptyText: gettext('From Default'), - }, - onWidgetAttach: 'setNodename', + }, + { + text: gettext('Bridge'), + dataIndex: 'bridge', + xtype: 'widgetcolumn', + flex: 1, + widget: { + xtype: 'PVE.form.BridgeSelector', + name: 'bridge', + isFormField: false, + autoSelect: false, + allowBlank: true, + emptyText: gettext('From Default'), }, - ], - }, - ], - }, - { - title: gettext('Resulting Config'), - reference: 'summaryTab', - items: [ - { - xtype: 'grid', - reference: 'summaryGrid', - maxHeight: 400, - scrollable: true, - store: { - model: 'KeyValue', - sorters: [{ - property: 'key', - direction: 'ASC', - }], + onWidgetAttach: 'setNodename', }, - columns: [ - { header: 'Key', width: 150, dataIndex: 'key' }, - { header: 'Value', flex: 1, dataIndex: 'value' }, - ], + ], + }, + { + xtype: 'proxmoxcheckbox', + name: 'uniqueMACs', + boxLabel: gettext('Unique MAC addresses'), + uncheckedValue: false, + value: false, + }, + ], + }, + { + 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' }, + ], + }, + ], + }, + ], + }], initComponent: function() { let me = this; @@ -770,6 +896,10 @@ Ext.define('PVE.window.GuestImport', { '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!'), + 'efi-state-lost': Ext.String.format( + gettext('EFI state cannot be imported, you may need to reconfigure the boot order (see {0})'), + 'OVMF/UEFI Boot Entries', + ), }; let message = warningsCatalogue[w.type]; if (!w.type || !message) { @@ -814,22 +944,41 @@ Ext.define('PVE.window.GuestImport', { } cdroms.push({ enable: true, + hidden: false, id, }); delete me.vmConfig[id]; } + me.lookup('diskGrid').getStore().setData(disks); me.lookup('netGrid').getStore().setData(nets); me.lookup('cdGrid').getStore().setData(cdroms); + let additionalCdIdx = me.getController().calculateAdditionalCDIdx(); + if (additionalCdIdx === '') { + me.getViewModel().set('maxCdDrives', true); + } else if (cdroms.length === 0) { + me.additionalCdIdx = additionalCdIdx; + me.lookup('cdGrid').getStore().add({ + enable: true, + hidden: !(me.vmConfig.ostype ?? '').startsWith('w'), + id: additionalCdIdx, + autogenerated: true, + }); + } + me.getViewModel().set('warnings', data.warnings.map(w => renderWarning(w))); let osinfo = PVE.Utils.get_kvm_osinfo(me.vmConfig.ostype ?? ''); + let prepareForVirtIO = (me.vmConfig.ostype ?? '').startsWith('w') && (me.vmConfig.bios ?? '').indexOf('ovmf') !== -1; me.setValues({ osbase: osinfo.base, ...me.vmConfig, }); + + + me.lookup('prepareForVirtIO').setValue(prepareForVirtIO); }, }); },