X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=www%2Fmanager6%2Fwindow%2FGuestImport.js;h=633af903cd03837073c13cb095104ad3f6ecdff2;hb=463b9b8219014e43121621d1aba52e6319ebf966;hp=f5809c32512691a21f92fb344e7dc0b7ca9fd16a;hpb=4fb223f75ed2a4f0472b4d582e0f033c396f2856;p=pve-manager.git diff --git a/www/manager6/window/GuestImport.js b/www/manager6/window/GuestImport.js index f5809c32..633af903 100644 --- a/www/manager6/window/GuestImport.js +++ b/www/manager6/window/GuestImport.js @@ -29,12 +29,12 @@ Ext.define('PVE.window.GuestImport', { widget.setNodename(view.nodename); }, - storageChange: function(storageSelector, value) { + diskStorageChange: function(storageSelector, value) { let me = this; let grid = me.lookup('diskGrid'); let rec = storageSelector.getWidgetRecord(); - let validFormats = storageSelector.store.getById(value).data.format; + let validFormats = storageSelector.store.getById(value)?.data.format; grid.query('pveDiskFormatSelector').some((selector) => { if (selector.getWidgetRecord().data.id !== rec.data.id) { return false; @@ -52,6 +52,25 @@ Ext.define('PVE.window.GuestImport', { }); }, + isoStorageChange: function(storageSelector, value) { + let me = this; + + let grid = me.lookup('cdGrid'); + let rec = storageSelector.getWidgetRecord(); + grid.query('pveFileSelector').some((selector) => { + if (selector.getWidgetRecord().data.id !== rec.data.id) { + return false; + } + + selector.setStorage(value); + if (!value) { + selector.setValue(''); + } + + return true; + }); + }, + onOSBaseChange: function(_field, value) { let me = this; let ostype = me.lookup('ostype'); @@ -65,6 +84,14 @@ 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 }))); + }, + control: { 'grid field': { // update records from widgetcolumns @@ -74,12 +101,18 @@ Ext.define('PVE.window.GuestImport', { rec.commit(); }, }, - 'pveStorageSelector': { - change: 'storageChange', + 'grid[reference=diskGrid] pveStorageSelector': { + change: 'diskStorageChange', + }, + 'grid[reference=cdGrid] pveStorageSelector': { + change: 'isoStorageChange', }, 'field[name=osbase]': { change: 'onOSBaseChange', }, + 'panel[reference=summaryTab]': { + activate: 'calculateConfig', + }, }, }, @@ -98,276 +131,482 @@ Ext.define('PVE.window.GuestImport', { }, }, + width: 700, + bodyPadding: 0, + items: [ { - xtype: 'inputpanel', - onGetValues: function(values) { - let me = this; - let grid = me.up('pveGuestImportWindow'); - - let config = Ext.apply(grid.vmConfig, values); - - if (config.scsi0) { - config.scsi0 = config.scsi0.replace('local:0,', 'local:0,format=qcow2,'); - } - - grid.lookup('diskGrid').getStore().each((rec) => { - if (!rec.data.enable) { - return; - } - let id = rec.data.id; - delete rec.data.enable; - delete rec.data.id; - rec.data.file += ':0'; // for our special api format - if (id === 'efidisk0') { - delete rec.data['import-from']; - } - config[id] = PVE.Parser.printQemuDrive(rec.data); - }); - - grid.lookup('netGrid').getStore().each((rec) => { - if (!rec.data.enable) { - return; - } - let id = rec.data.id; - delete rec.data.enable; - delete rec.data.id; - config[id] = PVE.Parser.printQemuNetwork(rec.data); - }); - - if (grid.lookup('liveimport').getValue()) { - config['live-restore'] = 1; - } - - return config; + xtype: 'tabpanel', + defaults: { + bodyPadding: 10, }, - - column1: [ - { - xtype: 'pveGuestIDSelector', - name: 'vmid', - fieldLabel: 'VM', - guestType: 'qemu', - loadNextFreeID: true, - }, + items: [ { - xtype: 'proxmoxintegerfield', - fieldLabel: gettext('Sockets'), - name: 'sockets', - reference: 'socketsField', - value: 1, - minValue: 1, - maxValue: 4, - allowBlank: true, - bind: { - value: '{socketCount}', + 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,'); + } + + grid.lookup('diskGrid').getStore().each((rec) => { + if (!rec.data.enable) { + return; + } + let id = rec.data.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']; + } + config[id] = PVE.Parser.printQemuDrive(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); + }); + + if (grid.lookup('liveimport').getValue()) { + config['live-restore'] = 1; + } + + return config; }, - }, - { - xtype: 'proxmoxintegerfield', - fieldLabel: gettext('Cores'), - name: 'cores', - reference: 'coresField', - value: 1, - minValue: 1, - maxValue: 128, - allowBlank: true, - bind: { - value: '{coreCount}', - }, - }, - { - xtype: 'pveMemoryField', - fieldLabel: gettext('Memory'), - name: 'memory', - reference: 'memoryField', - value: 512, - allowBlank: true, - }, - ], - 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('Type'), - }, - { - xtype: 'displayfield', - fieldLabel: gettext('Total cores'), - name: 'totalcores', - isFormField: false, - bind: { - value: '{totalCoreCount}', - }, - }, - { - 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', - store: { - fields: ['desc', 'val'], - data: PVE.Utils.kvm_ostypes.Linux, - }, - }, - ], - columnB: [ - { - xtype: 'displayfield', - fieldLabel: gettext('Disks'), - labelWidth: 200, - }, - { - xtype: 'grid', - reference: 'diskGrid', - maxHeight: 150, - store: { data: [] }, - columns: [ + column1: [ { - xtype: 'checkcolumn', - header: gettext('Use'), - width: 50, - dataIndex: 'enable', - listeners: { - checkchange: function(_column, _rowIndex, _checked, record) { - record.commit(); - }, + 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}', + }, + }, + { + xtype: 'proxmoxintegerfield', + fieldLabel: gettext('Cores'), + name: 'cores', + reference: 'coresField', + value: 1, + minValue: 1, + maxValue: 128, + allowBlank: true, + bind: { + value: '{coreCount}', }, }, { - text: gettext('Disk'), - dataIndex: 'id', + xtype: 'pveMemoryField', + fieldLabel: gettext('Memory'), + name: 'memory', + reference: 'memoryField', + value: 512, + allowBlank: true, + }, + { + //spacer + xtype: 'displayfield', + }, + { + 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('Type'), }, { - text: gettext('Source'), - dataIndex: 'import-from', - flex: 1, - renderer: function(value) { - return value.replace(/^.*\//, ''); + xtype: 'displayfield', + fieldLabel: gettext('Total cores'), + name: 'totalcores', + isFormField: false, + bind: { + value: '{totalCoreCount}', }, }, { - text: gettext('Storage'), - dataIndex: 'file', - xtype: 'widgetcolumn', - width: 150, - widget: { - xtype: 'pveStorageSelector', - isFormField: false, - name: 'file', - storageContent: 'images', + 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', + store: { + fields: ['desc', 'val'], + data: PVE.Utils.kvm_ostypes.Linux, }, - onWidgetAttach: 'setNodename', }, { - text: gettext('Format'), - dataIndex: 'format', - xtype: 'widgetcolumn', - width: 150, - widget: { - xtype: 'pveDiskFormatSelector', - name: 'format', - isFormField: false, - matchFieldWidth: false, + 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, + boxLabel: gettext('Experimental'), + }, + { + xtype: 'displayfield', + fieldLabel: gettext('Warnings'), + labelWidth: 200, + hidden: true, + bind: { + hidden: '{hideWarnings}', + }, + }, + { + xtype: 'displayfield', + reference: 'warningText', + userCls: 'pmx-hint', + hidden: true, + bind: { + hidden: '{hideWarnings}', + value: '{warningsText}', }, }, ], }, { - xtype: 'displayfield', - fieldLabel: gettext('Network Interfaces'), - labelWidth: 200, - }, - { - xtype: 'grid', - maxHeight: 150, - reference: 'netGrid', - store: { data: [] }, - columns: [ + title: gettext('Advanced'), + xtype: 'inputpanel', + items: [ { - xtype: 'checkcolumn', - header: gettext('Use'), - width: 50, - dataIndex: 'enable', - listeners: { - checkchange: function(_column, _rowIndex, _checked, record) { - record.commit(); - }, + xtype: 'displayfield', + fieldLabel: gettext('Disks'), + labelWidth: 200, + }, + { + xtype: 'grid', + reference: 'diskGrid', + minHeight: 58, + 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', + }, + { + 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, + }, + }, + ], }, { - text: gettext('ID'), - dataIndex: 'id', + xtype: 'displayfield', + fieldLabel: gettext('CD/DVD Drives'), + labelWidth: 200, }, { - text: gettext('MAC address'), - flex: 1, - dataIndex: 'macaddr', + xtype: 'grid', + reference: 'cdGrid', + minHeight: 58, + 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('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('ISO'), + dataIndex: 'file', + xtype: 'widgetcolumn', + flex: 1, + widget: { + xtype: 'pveFileSelector', + name: 'file', + isFormField: false, + allowBlank: true, + emptyText: Proxmox.Utils.noneText, + storageContent: 'iso', + }, + onWidgetAttach: 'setNodename', + }, + ], }, { - text: gettext('Model'), - flex: 1, - dataIndex: 'model', + xtype: 'displayfield', + fieldLabel: gettext('Network Interfaces'), + labelWidth: 200, }, { - text: gettext('Bridge'), - dataIndex: 'bridge', - xtype: 'widgetcolumn', - widget: { - xtype: 'PVE.form.BridgeSelector', - name: 'bridge', - isFormField: false, - allowBlank: false, + xtype: 'grid', + minHeight: 58, + maxHeight: 150, + reference: 'netGrid', + store: { + data: [], + sorters: [ + 'id', + ], }, - onWidgetAttach: 'setNodename', + 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('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', + }, + ], }, ], }, { - xtype: 'displayfield', - fieldLabel: gettext('Warnings'), - labelWidth: 200, - hidden: true, - bind: { - hidden: '{hideWarnings}', - }, - }, - { - xtype: 'displayfield', - reference: 'warningText', - userCls: 'pmx-hint', - hidden: true, - bind: { - hidden: '{hideWarnings}', - value: '{warningsText}', - }, + 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' }, + ], + }, + ], }, ], }, @@ -390,18 +629,14 @@ 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); + me.lookup('defaultBridge').setNodename(me.nodename); + 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"), @@ -420,10 +655,17 @@ Ext.define('PVE.window.GuestImport', { let disks = []; for (const [id, value] of Object.entries(data.disks ?? {})) { + let volid = Ext.htmlEncode(''); + let size = 'auto'; + if (Ext.isObject(value)) { + volid = value.volid; + size = value.size; + } disks.push({ id, enable: true, - 'import-from': id === 'efidisk0' ? Ext.htmlEncode('') : value, + size, + 'import-from': volid, format: 'raw', }); } @@ -434,8 +676,21 @@ Ext.define('PVE.window.GuestImport', { parsed.enable = true; nets.push(parsed); } + + let cdroms = []; + for (const [id, value] of Object.entries(me.vmConfig)) { + if (!Ext.isString(value) || !value.match(/media=cdrom/)) { + continue; + } + cdroms.push({ + enable: true, + id, + }); + delete me.vmConfig[id]; + } me.lookup('diskGrid').getStore().setData(disks); me.lookup('netGrid').getStore().setData(nets); + me.lookup('cdGrid').getStore().setData(cdroms); me.getViewModel().set('warnings', data.warnings.map(w => renderWarning(w)));