]>
git.proxmox.com Git - pve-manager.git/blob - www/manager6/window/GuestImport.js
1 Ext
.define('PVE.window.GuestImport', {
2 extend
: 'Proxmox.window.Edit', // fixme: Proxmox.window.Edit?
3 alias
: 'widget.pveGuestImportWindow',
5 title
: gettext('Import Guest'),
7 submitUrl: function() {
9 return `/nodes/${me.nodename}/qemu`;
14 submitText
: gettext('Import'),
18 loadUrl: function(_url
, { storage
, nodename
, volumeName
}) {
19 let args
= Ext
.Object
.toQueryString({ volume
: volumeName
});
20 return `/nodes/${nodename}/storage/${storage}/import-metadata?${args}`;
24 xclass
: 'Ext.app.ViewController',
26 setNodename: function(_column
, widget
) {
28 let view
= me
.getView();
29 widget
.setNodename(view
.nodename
);
32 diskStorageChange: function(storageSelector
, value
) {
35 let grid
= me
.lookup('diskGrid');
36 let rec
= storageSelector
.getWidgetRecord();
37 let validFormats
= storageSelector
.store
.getById(value
)?.data
.format
;
38 grid
.query('pveDiskFormatSelector').some((selector
) => {
39 if (selector
.getWidgetRecord().data
.id
!== rec
.data
.id
) {
43 if (validFormats
?.[0]?.qcow2
) {
44 selector
.setDisabled(false);
45 selector
.setValue('qcow2');
47 selector
.setValue('raw');
48 selector
.setDisabled(true);
55 isoStorageChange: function(storageSelector
, value
) {
58 let grid
= me
.lookup('cdGrid');
59 let rec
= storageSelector
.getWidgetRecord();
60 grid
.query('pveFileSelector').some((selector
) => {
61 if (selector
.getWidgetRecord().data
.id
!== rec
.data
.id
) {
65 selector
.setStorage(value
);
67 selector
.setValue('');
74 onOSBaseChange: function(_field
, value
) {
76 let ostype
= me
.lookup('ostype');
77 let store
= ostype
.getStore();
78 store
.setData(PVE
.Utils
.kvm_ostypes
[value
]);
79 let old_val
= ostype
.getValue();
80 if (old_val
&& store
.find('val', old_val
) !== -1) {
81 ostype
.setValue(old_val
);
83 ostype
.setValue(store
.getAt(0));
87 calculateConfig: function() {
89 let inputPanel
= me
.lookup('mainInputPanel');
90 let summaryGrid
= me
.lookup('summaryGrid');
91 let values
= inputPanel
.getValues();
92 summaryGrid
.getStore().setData(Object
.entries(values
).map(([key
, value
]) => ({ key
, value
})));
95 calculateAdditionalCDIdx: function() {
98 let maxIde
= me
.getMaxControllerId('ide');
99 let maxSata
= me
.getMaxControllerId('sata');
100 // only ide0 and ide2 can be used reliably for isos (e.g. for q35)
107 if (maxSata
< PVE
.Utils
.diskControllerMaxIDs
.sata
- 1) {
108 return `sata${maxSata+1}`;
114 // assume assigned sata disks indices are continuous, so without holes
115 getMaxControllerId: function(controller
) {
117 let view
= me
.getView();
122 let max
= view
[`max${controller}`];
123 if (max
!== undefined) {
128 for (const key
of Object
.keys(me
.getView().vmConfig
)) {
129 if (!key
.toLowerCase().startsWith(controller
)) {
132 let idx
= parseInt(key
.slice(controller
.length
), 10);
137 me
.lookup('diskGrid').getStore().each(rec
=> {
138 if (!rec
.data
.id
.toLowerCase().startsWith(controller
)) {
141 let idx
= parseInt(rec
.data
.id
.slice(controller
.length
), 10);
146 me
.lookup('cdGrid').getStore().each(rec
=> {
147 if (!rec
.data
.id
.toLowerCase().startsWith(controller
) || rec
.data
.hidden
) {
150 let idx
= parseInt(rec
.data
.id
.slice(controller
.length
), 10);
156 view
[`max${controller}`] = max
;
160 mapDisk: function(value
, metaData
) {
162 let mapSata
= me
.lookup('mapSata');
163 if (mapSata
.isDisabled() || !mapSata
.getValue()) {
166 if (!value
.toLowerCase().startsWith('scsi')) {
169 let offset
= parseInt(value
.slice(4), 10);
170 let newIdx
= offset
+ me
.getMaxControllerId('sata') + 1;
171 if (me
.getViewModel().get('isWindows') && me
.getView().additionalCdIdx
?.startsWith('sata')) {
172 // additionalCdIdx takes the highest sata port
175 if (newIdx
>= PVE
.Utils
.diskControllerMaxIDs
.sata
) {
177 if (metaData
!== undefined) {
178 // we're in the renderer so put a warning here
179 let warning
= gettext('Too many disks, could not map to SATA.');
180 prefix
= `<i data-qtip="${warning}" class="fa fa-exclamation-triangle warning"></i> `;
182 return `${prefix}${value}`;
184 return `sata${newIdx}`;
187 refreshGrids: function() {
188 this.lookup('diskGrid').reconfigure();
189 this.lookup('cdGrid').reconfigure();
190 this.lookup('netGrid').reconfigure();
193 onOSTypeChange: function(_cb
, value
) {
198 let store
= me
.lookup('cdGrid').getStore();
199 let collection
= store
.getData().getSource() ?? store
.getData();
200 let rec
= collection
.find('autogenerated', true);
202 let isWindows
= (value
?? '').startsWith('w');
204 rec
.set('hidden', !isWindows
);
207 let prepareVirtio
= me
.lookup('mapSata').getValue();
208 let defaultScsiHw
= me
.getView().vmConfig
.scsihw
?? '__default__';
209 me
.lookup('scsihw').setValue(prepareVirtio
&& isWindows
? 'virtio-scsi-single' : defaultScsiHw
);
214 onPrepareVirtioChange: function(_cb
, value
) {
217 let scsihw
= me
.lookup('scsihw');
218 scsihw
.suspendEvents();
219 scsihw
.setValue(value
? 'virtio-scsi-single' : me
.getView().vmConfig
.scsihw
);
220 scsihw
.resumeEvents();
225 onScsiHwChange: function(_field
, value
) {
227 me
.getView().vmConfig
.scsihw
= value
;
230 onUniqueMACChange: function(_cb
, value
) {
233 me
.getViewModel().set('uniqueMACAdresses', value
);
235 me
.lookup('netGrid').reconfigure();
238 renderMacAddress: function(value
, metaData
, record
, rowIndex
, colIndex
, store
, view
) {
240 let vm
= me
.getViewModel();
242 return !vm
.get('uniqueMACAdresses') && value
? value
: 'auto';
247 // update records from widgetcolumns
248 change: function(widget
, value
) {
249 let rec
= widget
.getWidgetRecord();
250 rec
.set(widget
.name
, value
);
254 'grid[reference=diskGrid] pveStorageSelector': {
255 change
: 'diskStorageChange',
257 'grid[reference=cdGrid] pveStorageSelector': {
258 change
: 'isoStorageChange',
260 'field[name=osbase]': {
261 change
: 'onOSBaseChange',
263 'panel[reference=summaryTab]': {
264 activate
: 'calculateConfig',
266 'proxmoxcheckbox[reference=mapSata]': {
267 change
: 'onPrepareVirtioChange',
269 'combobox[name=ostype]': {
270 change
: 'onOSTypeChange',
272 'pveScsiHwSelector': {
273 change
: 'onScsiHwChange',
275 'proxmoxcheckbox[name=uniqueMACs]': {
276 change
: 'onUniqueMACChange',
288 uniqueMACAdresses
: false,
293 totalCoreCount
: get => get('socketCount') * get('coreCount'),
294 hideWarnings
: get => get('warnings').length
=== 0,
295 warningsText
: get => '<ul style="margin: 0; padding-left: 20px;">'
296 + get('warnings').map(w
=> `<li>${w}</li>`).join('') + '</ul>',
297 liveImportNote
: get => !get('liveImport') ? ''
298 : gettext('Note: If anything goes wrong during the live-import, new data written by the VM may be lost.'),
299 isWindows
: get => (get('os') ?? '').startsWith('w'),
313 title
: gettext('General'),
315 reference
: 'mainInputPanel',
316 onGetValues: function(values
) {
318 let grid
= me
.up('pveGuestImportWindow');
319 let vm
= grid
.getViewModel();
321 // from pveDiskStorageSelector
322 let defaultStorage
= values
.hdstorage
;
323 let defaultFormat
= values
.diskformat
;
324 delete values
.hdstorage
;
325 delete values
.diskformat
;
327 let defaultBridge
= values
.defaultBridge
;
328 delete values
.defaultBridge
;
330 let config
= Ext
.apply(grid
.vmConfig
, values
);
333 config
.scsi0
= config
.scsi0
.replace('local:0,', 'local:0,format=qcow2,');
336 let parsedBoot
= PVE
.Parser
.parsePropertyString(config
.boot
?? '');
337 if (parsedBoot
.order
) {
338 parsedBoot
.order
= parsedBoot
.order
.split(';');
341 grid
.lookup('diskGrid').getStore().each((rec
) => {
342 if (!rec
.data
.enable
) {
345 let id
= grid
.getController().mapDisk(rec
.data
.id
);
346 if (id
!== rec
.data
.id
&& parsedBoot
?.order
) {
347 let idx
= parsedBoot
.order
.indexOf(rec
.data
.id
);
349 parsedBoot
.order
[idx
] = id
;
359 data
.file
= defaultStorage
;
360 data
.format
= defaultFormat
;
362 data
.file
+= ':0'; // for our special api format
363 if (id
=== 'efidisk0') {
364 delete data
['import-from'];
366 config
[id
] = PVE
.Parser
.printQemuDrive(data
);
369 if (parsedBoot
.order
) {
370 parsedBoot
.order
= parsedBoot
.order
.join(';');
372 config
.boot
= PVE
.Parser
.printPropertyString(parsedBoot
);
374 grid
.lookup('netGrid').getStore().each((rec
) => {
375 if (!rec
.data
.enable
) {
378 let id
= rec
.data
.id
;
385 data
.bridge
= defaultBridge
;
387 if (vm
.get('uniqueMACAdresses')) {
388 data
.macaddr
= undefined;
390 config
[id
] = PVE
.Parser
.printQemuNetwork(data
);
393 grid
.lookup('cdGrid').getStore().each((rec
) => {
394 if (!rec
.data
.enable
) {
397 let id
= rec
.data
.id
;
400 file
: rec
.data
.file
? rec
.data
.file
: 'none',
402 config
[id
] = PVE
.Parser
.printPropertyString(cd
);
405 config
.scsihw
= grid
.lookup('scsihw').getValue();
407 if (grid
.lookup('liveimport').getValue()) {
408 config
['live-restore'] = 1;
411 // remove __default__ values
412 for (const [key
, value
] of Object
.entries(config
)) {
413 if (value
=== '__default__') {
423 xtype
: 'pveGuestIDSelector',
427 loadNextFreeID
: true,
430 xtype
: 'proxmoxintegerfield',
431 fieldLabel
: gettext('Sockets'),
433 reference
: 'socketsField',
439 value
: '{socketCount}',
443 xtype
: 'proxmoxintegerfield',
444 fieldLabel
: gettext('Cores'),
446 reference
: 'coresField',
452 value
: '{coreCount}',
456 xtype
: 'pveMemoryField',
457 fieldLabel
: gettext('Memory') + ' (MiB)',
459 reference
: 'memoryField',
463 { xtype
: 'displayfield' }, // spacer
464 { xtype
: 'displayfield' }, // spacer
466 xtype
: 'pveDiskStorageSelector',
467 reference
: 'defaultStorage',
468 storageLabel
: gettext('Default Storage'),
469 storageContent
: 'images',
472 name
: 'defaultStorage',
479 fieldLabel
: gettext('Name'),
482 reference
: 'nameField',
486 xtype
: 'CPUModelSelector',
488 reference
: 'cputype',
489 value
: 'x86-64-v2-AES',
490 fieldLabel
: gettext('CPU Type'),
493 xtype
: 'displayfield',
494 fieldLabel
: gettext('Total cores'),
498 value
: '{totalCoreCount}',
505 fieldLabel
: gettext('OS Type'),
509 store
: Object
.keys(PVE
.Utils
.kvm_ostypes
),
515 fieldLabel
: gettext('Version'),
521 displayField
: 'desc',
526 fields
: ['desc', 'val'],
527 data
: PVE
.Utils
.kvm_ostypes
.Linux
,
530 { xtype
: 'displayfield' }, // spacer
532 xtype
: 'PVE.form.BridgeSelector',
533 reference
: 'defaultBridge',
534 name
: 'defaultBridge',
536 fieldLabel
: gettext('Default Bridge'),
542 xtype
: 'proxmoxcheckbox',
543 fieldLabel
: gettext('Live Import'),
544 reference
: 'liveimport',
546 boxLabelCls
: 'pmx-hint black x-form-cb-label',
548 value
: '{liveImport}',
549 boxLabel
: '{liveImportNote}',
553 xtype
: 'displayfield',
554 fieldLabel
: gettext('Warnings'),
558 hidden
: '{hideWarnings}',
562 xtype
: 'displayfield',
563 reference
: 'warningText',
567 hidden
: '{hideWarnings}',
568 value
: '{warningsText}',
574 title
: gettext('Advanced'),
577 // the first inputpanel handles all values, so prevent value leakage here
578 onGetValues
: () => ({}),
582 xtype
: 'displayfield',
583 fieldLabel
: gettext('Disks'),
588 reference
: 'diskGrid',
599 xtype
: 'checkcolumn',
600 header
: gettext('Use'),
604 checkchange: function(_column
, _rowIndex
, _checked
, record
) {
610 text
: gettext('Disk'),
615 text
: gettext('Source'),
616 dataIndex
: 'import-from',
618 renderer: function(value
) {
619 return value
.replace(/^.*\//, '');
623 text
: gettext('Size'),
625 renderer
: (value
) => {
626 if (Ext
.isNumeric(value
)) {
627 return Proxmox
.Utils
.render_size(value
);
629 return value
?? Proxmox
.Utils
.unknownText
;
633 text
: gettext('Storage'),
635 xtype
: 'widgetcolumn',
638 xtype
: 'pveStorageSelector',
642 emptyText
: gettext('From Default'),
644 storageContent
: 'images',
646 onWidgetAttach
: 'setNodename',
649 text
: gettext('Format'),
651 xtype
: 'widgetcolumn',
654 xtype
: 'pveDiskFormatSelector',
658 matchFieldWidth
: false,
667 xtype
: 'proxmoxcheckbox',
668 fieldLabel
: gettext('Prepare for VirtIO-SCSI'),
670 reference
: 'mapSata',
675 disabled
: '{!isWindows}',
679 'data-qtip': gettext('Maps SCSI disks to SATA and changes the SCSI Controller. Useful for a quicker switch to VirtIO-SCSI attached disks'),
686 xtype
: 'pveScsiHwSelector',
689 value
: '__default__',
691 fieldLabel
: gettext('SCSI Controller'),
697 xtype
: 'displayfield',
698 fieldLabel
: gettext('CD/DVD Drives'),
713 return !rec
.data
.hidden
;
719 xtype
: 'checkcolumn',
720 header
: gettext('Use'),
724 checkchange: function(_column
, _rowIndex
, _checked
, record
) {
730 text
: gettext('Slot'),
735 text
: gettext('Storage'),
736 xtype
: 'widgetcolumn',
739 xtype
: 'pveStorageSelector',
743 emptyText
: Proxmox
.Utils
.noneText
,
744 storageContent
: 'iso',
746 onWidgetAttach
: 'setNodename',
749 text
: gettext('ISO'),
751 xtype
: 'widgetcolumn',
754 xtype
: 'pveFileSelector',
758 emptyText
: Proxmox
.Utils
.noneText
,
759 storageContent
: 'iso',
761 onWidgetAttach
: 'setNodename',
766 xtype
: 'displayfield',
767 fieldLabel
: gettext('Network Interfaces'),
777 reference
: 'netGrid',
786 xtype
: 'checkcolumn',
787 header
: gettext('Use'),
791 checkchange: function(_column
, _rowIndex
, _checked
, record
) {
801 text
: gettext('MAC address'),
803 dataIndex
: 'macaddr',
804 renderer
: 'renderMacAddress',
807 text
: gettext('Model'),
810 xtype
: 'widgetcolumn',
812 xtype
: 'pveNetworkCardSelector',
819 text
: gettext('Bridge'),
821 xtype
: 'widgetcolumn',
824 xtype
: 'PVE.form.BridgeSelector',
829 emptyText
: gettext('From Default'),
831 onWidgetAttach
: 'setNodename',
836 xtype
: 'proxmoxcheckbox',
838 boxLabel
: gettext('Unique MAC addresses'),
839 uncheckedValue
: false,
845 title
: gettext('Resulting Config'),
846 reference
: 'summaryTab',
850 reference
: 'summaryGrid',
861 { header
: 'Key', width
: 150, dataIndex
: 'key' },
862 { header
: 'Value', flex
: 1, dataIndex
: 'value' },
870 initComponent: function() {
873 if (!me
.volumeName
) {
874 throw "no volumeName given";
878 throw "no storage given";
882 throw "no nodename given";
887 me
.setTitle(Ext
.String
.format(gettext('Import Guest - {0}'), `${me.storage}:${me.volumeName}`));
889 me
.lookup('defaultStorage').setNodename(me
.nodename
);
890 me
.lookup('defaultBridge').setNodename(me
.nodename
);
892 let renderWarning
= w
=> {
893 const warningsCatalogue
= {
894 'cdrom-image-ignored': gettext("CD-ROM images cannot get imported, if required you can reconfigure the '{0}' drive in the 'Advanced' tab."),
895 'nvme-unsupported': gettext("NVMe disks are currently not supported, '{0}' will get attaced as SCSI"),
896 'ovmf-with-lsi-unsupported': gettext("OVMF is built without LSI drivers, scsi hardware was set to '{1}'"),
897 'serial-port-socket-only': gettext("Serial socket '{0}' will be mapped to a socket"),
898 'guest-is-running': gettext('Virtual guest seems to be running on source host. Import might fail or have inconsistent state!'),
899 'efi-state-lost': Ext
.String
.format(
900 gettext('EFI state cannot be imported, you may need to reconfigure the boot order (see {0})'),
901 '<a href="https://pve.proxmox.com/wiki/OVMF/UEFI_Boot_Entries">OVMF/UEFI Boot Entries</a>',
904 let message
= warningsCatalogue
[w
.type
];
905 if (!w
.type
|| !message
) {
906 return w
.message
?? w
.type
?? gettext('Unknown warning');
908 return Ext
.String
.format(message
, w
.key
?? 'unknown', w
.value
?? 'unknown');
912 success: function(response
) {
913 let data
= response
.result
.data
;
914 me
.vmConfig
= data
['create-args'];
917 for (const [id
, value
] of Object
.entries(data
.disks
?? {})) {
918 let volid
= Ext
.htmlEncode('<none>');
920 if (Ext
.isObject(value
)) {
928 'import-from': volid
,
934 for (const [id
, parsed
] of Object
.entries(data
.net
?? {})) {
936 parsed
.enable
= true;
941 for (const [id
, value
] of Object
.entries(me
.vmConfig
)) {
942 if (!Ext
.isString(value
) || !value
.match(/media=cdrom/)) {
950 delete me
.vmConfig
[id
];
953 me
.lookup('diskGrid').getStore().setData(disks
);
954 me
.lookup('netGrid').getStore().setData(nets
);
955 me
.lookup('cdGrid').getStore().setData(cdroms
);
957 let additionalCdIdx
= me
.getController().calculateAdditionalCDIdx();
958 if (additionalCdIdx
=== '') {
959 me
.getViewModel().set('maxCdDrives', true);
960 } else if (cdroms
.length
=== 0) {
961 me
.additionalCdIdx
= additionalCdIdx
;
962 me
.lookup('cdGrid').getStore().add({
964 hidden
: !(me
.vmConfig
.ostype
?? '').startsWith('w'),
970 me
.getViewModel().set('warnings', data
.warnings
.map(w
=> renderWarning(w
)));
972 let osinfo
= PVE
.Utils
.get_kvm_osinfo(me
.vmConfig
.ostype
?? '');
973 let mapSata
= (me
.vmConfig
.ostype
?? '').startsWith('w') && (me
.vmConfig
.bios
?? '').indexOf('ovmf') !== -1;
981 me
.lookup('mapSata').setValue(mapSata
);