]>
git.proxmox.com Git - pve-manager.git/blob - www/manager6/window/GuestImport.js
ec6079c2c461c450d80b4f7b8e620424e2e2a3d2
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 // assume assigned sata disks indices are continuous, so without holes
96 getMaxSata: function() {
98 let view
= me
.getView();
99 if (view
.maxSata
!== undefined) {
104 for (const key
of Object
.keys(me
.getView().vmConfig
)) {
105 if (!key
.toLowerCase().startsWith('sata')) {
108 let idx
= parseInt(key
.slice(4), 10);
109 if (idx
> view
.maxSata
) {
113 me
.lookup('diskGrid').getStore().each(rec
=> {
114 if (!rec
.data
.id
.toLowerCase().startsWith('sata')) {
117 let idx
= parseInt(rec
.data
.id
.slice(4), 10);
118 if (idx
> view
.maxSata
) {
122 me
.lookup('cdGrid').getStore().each(rec
=> {
123 if (!rec
.data
.id
.toLowerCase().startsWith('sata')) {
126 let idx
= parseInt(rec
.data
.id
.slice(4), 10);
127 if (idx
> view
.maxSata
) {
135 mapDisk: function(value
, metaData
) {
137 let mapSata
= me
.lookup('mapSata');
138 if (mapSata
.isDisabled() || !mapSata
.getValue()) {
141 if (!value
.toLowerCase().startsWith('scsi')) {
144 let offset
= parseInt(value
.slice(4), 10);
145 let newIdx
= offset
+ me
.getMaxSata() + 1;
146 if (newIdx
> PVE
.Utils
.diskControllerMaxIDs
.sata
) {
148 if (metaData
!== undefined) {
149 // we're in the renderer so put a warning here
150 let warning
= gettext('Too many disks, could not map to SATA.');
151 prefix
= `<i data-qtip="${warning}" class="fa fa-exclamation-triangle warning"></i> `;
153 return `${prefix}${value}`;
155 return `sata${newIdx}`;
158 refreshDiskGrid: function() {
159 this.lookup('diskGrid').reconfigure();
162 toggleIsoSelector: function(_cb
, value
) {
164 me
.lookup('isoSelector').setDisabled(!value
);
165 me
.lookup('isoSelector').setHidden(!value
);
170 // update records from widgetcolumns
171 change: function(widget
, value
) {
172 let rec
= widget
.getWidgetRecord();
173 rec
.set(widget
.name
, value
);
177 'grid[reference=diskGrid] pveStorageSelector': {
178 change
: 'diskStorageChange',
180 'grid[reference=cdGrid] pveStorageSelector': {
181 change
: 'isoStorageChange',
183 'field[name=osbase]': {
184 change
: 'onOSBaseChange',
186 'panel[reference=summaryTab]': {
187 activate
: 'calculateConfig',
189 'proxmoxcheckbox[reference=mapSata]': {
190 change
: 'refreshDiskGrid',
192 'combobox[name=ostype]': {
193 change
: 'refreshDiskGrid',
195 'proxmoxcheckbox[reference=enableSecondCD]': {
196 change
: 'toggleIsoSelector',
211 totalCoreCount
: get => get('socketCount') * get('coreCount'),
212 hideWarnings
: get => get('warnings').length
=== 0,
213 warningsText
: get => '<ul style="margin: 0; padding-left: 20px;">'
214 + get('warnings').map(w
=> `<li>${w}</li>`).join('') + '</ul>',
215 liveImportNote
: get => !get('liveImport') ? ''
216 : gettext('Note: If anything goes wrong during the live-import, new data written by the VM may be lost.'),
217 isWindows
: get => (get('os') ?? '').startsWith('win'),
232 title
: gettext('General'),
234 reference
: 'mainInputPanel',
235 onGetValues: function(values
) {
237 let grid
= me
.up('pveGuestImportWindow');
239 // from pveDiskStorageSelector
240 let defaultStorage
= values
.hdstorage
;
241 let defaultFormat
= values
.diskformat
;
242 delete values
.hdstorage
;
243 delete values
.diskformat
;
245 let defaultBridge
= values
.defaultBridge
;
246 delete values
.defaultBridge
;
248 let config
= Ext
.apply(grid
.vmConfig
, values
);
251 config
.scsi0
= config
.scsi0
.replace('local:0,', 'local:0,format=qcow2,');
254 let parsedBoot
= PVE
.Parser
.parsePropertyString(config
.boot
?? '');
255 if (parsedBoot
.order
) {
256 parsedBoot
.order
= parsedBoot
.order
.split(';');
259 grid
.lookup('diskGrid').getStore().each((rec
) => {
260 if (!rec
.data
.enable
) {
263 let id
= grid
.getController().mapDisk(rec
.data
.id
);
264 if (id
!== rec
.data
.id
&& parsedBoot
?.order
) {
265 let idx
= parsedBoot
.order
.indexOf(rec
.data
.id
);
267 parsedBoot
.order
[idx
] = id
;
277 data
.file
= defaultStorage
;
278 data
.format
= defaultFormat
;
280 data
.file
+= ':0'; // for our special api format
281 if (id
=== 'efidisk0') {
282 delete data
['import-from'];
284 config
[id
] = PVE
.Parser
.printQemuDrive(data
);
287 if (parsedBoot
.order
) {
288 parsedBoot
.order
= parsedBoot
.order
.join(';');
290 config
.boot
= PVE
.Parser
.printPropertyString(parsedBoot
);
292 grid
.lookup('netGrid').getStore().each((rec
) => {
293 if (!rec
.data
.enable
) {
296 let id
= rec
.data
.id
;
303 data
.bridge
= defaultBridge
;
305 config
[id
] = PVE
.Parser
.printQemuNetwork(data
);
308 grid
.lookup('cdGrid').getStore().each((rec
) => {
309 if (!rec
.data
.enable
) {
312 let id
= rec
.data
.id
;
315 file
: rec
.data
.file
? rec
.data
.file
: 'none',
317 config
[id
] = PVE
.Parser
.printPropertyString(cd
);
320 config
.scsihw
= grid
.lookup('scsihw').getValue();
322 if (grid
.lookup('liveimport').getValue()) {
323 config
['live-restore'] = 1;
326 if (grid
.lookup('enableSecondCD')) {
327 let idsToTry
= ['ide0', 'ide2'];
328 for (let i
= 0; i
<=PVE
.Utils
.diskControllerMaxIDs
.sata
; i
++) {
329 idsToTry
.push(`sata{$i}`);
332 for (const id
of idsToTry
) {
334 config
[id
] = PVE
.Parser
.printQemuDrive({
336 file
: grid
.lookup('isoSelector').getValue(),
343 console
.warn('could not insert cd drive for virtio');
347 // remove __default__ values
348 for (const [key
, value
] of Object
.entries(config
)) {
349 if (value
=== '__default__') {
359 xtype
: 'pveGuestIDSelector',
363 loadNextFreeID
: true,
366 xtype
: 'proxmoxintegerfield',
367 fieldLabel
: gettext('Sockets'),
369 reference
: 'socketsField',
375 value
: '{socketCount}',
379 xtype
: 'proxmoxintegerfield',
380 fieldLabel
: gettext('Cores'),
382 reference
: 'coresField',
388 value
: '{coreCount}',
392 xtype
: 'pveMemoryField',
393 fieldLabel
: gettext('Memory'),
395 reference
: 'memoryField',
401 xtype
: 'displayfield',
404 xtype
: 'pveDiskStorageSelector',
405 reference
: 'defaultStorage',
406 storageLabel
: gettext('Default Storage'),
407 storageContent
: 'images',
410 name
: 'defaultStorage',
417 fieldLabel
: gettext('Name'),
420 reference
: 'nameField',
424 xtype
: 'CPUModelSelector',
426 reference
: 'cputype',
427 value
: 'x86-64-v2-AES',
428 fieldLabel
: gettext('Type'),
431 xtype
: 'displayfield',
432 fieldLabel
: gettext('Total cores'),
436 value
: '{totalCoreCount}',
443 fieldLabel
: gettext('OS Type'),
447 store
: Object
.keys(PVE
.Utils
.kvm_ostypes
),
453 fieldLabel
: gettext('Version'),
459 displayField
: 'desc',
464 fields
: ['desc', 'val'],
465 data
: PVE
.Utils
.kvm_ostypes
.Linux
,
469 xtype
: 'PVE.form.BridgeSelector',
470 reference
: 'defaultBridge',
471 name
: 'defaultBridge',
473 fieldLabel
: gettext('Default Bridge'),
479 xtype
: 'proxmoxcheckbox',
480 fieldLabel
: gettext('Live Import'),
481 reference
: 'liveimport',
483 boxLabelCls
: 'pmx-hint black x-form-cb-label',
485 value
: '{liveImport}',
486 boxLabel
: '{liveImportNote}',
490 xtype
: 'displayfield',
491 fieldLabel
: gettext('Warnings'),
495 hidden
: '{hideWarnings}',
499 xtype
: 'displayfield',
500 reference
: 'warningText',
504 hidden
: '{hideWarnings}',
505 value
: '{warningsText}',
511 title
: gettext('Advanced'),
516 xtype
: 'pveScsiHwSelector',
520 fieldLabel
: gettext('SCSI Controller'),
523 xtype
: 'proxmoxcheckbox',
524 reference
: 'enableSecondCD',
528 boxLabel
: gettext('Add additional drive for VirtIO drivers'),
530 hidden
: '{!isWindows}',
531 disabled
: '{!isWindows}',
538 xtype
: 'proxmoxcheckbox',
539 fieldLabel
: gettext('Map SCSI to SATA'),
541 reference
: 'mapSata',
546 hidden
: '{!isWindows}',
547 disabled
: '{!isWindows}',
551 'data-qtip': gettext('Useful when wanting to use VirtIO-SCSI'),
555 xtype
: 'pveIsoSelector',
556 reference
: 'isoSelector',
568 xtype
: 'displayfield',
569 fieldLabel
: gettext('Disks'),
574 reference
: 'diskGrid',
585 xtype
: 'checkcolumn',
586 header
: gettext('Use'),
590 checkchange: function(_column
, _rowIndex
, _checked
, record
) {
596 text
: gettext('Disk'),
601 text
: gettext('Source'),
602 dataIndex
: 'import-from',
604 renderer: function(value
) {
605 return value
.replace(/^.*\//, '');
609 text
: gettext('Size'),
611 renderer
: (value
) => {
612 if (Ext
.isNumeric(value
)) {
613 return Proxmox
.Utils
.render_size(value
);
615 return value
?? Proxmox
.Utils
.unknownText
;
619 text
: gettext('Storage'),
621 xtype
: 'widgetcolumn',
624 xtype
: 'pveStorageSelector',
628 emptyText
: gettext('From Default'),
630 storageContent
: 'images',
632 onWidgetAttach
: 'setNodename',
635 text
: gettext('Format'),
637 xtype
: 'widgetcolumn',
640 xtype
: 'pveDiskFormatSelector',
644 matchFieldWidth
: false,
650 xtype
: 'displayfield',
651 fieldLabel
: gettext('CD/DVD Drives'),
670 xtype
: 'checkcolumn',
671 header
: gettext('Use'),
675 checkchange: function(_column
, _rowIndex
, _checked
, record
) {
681 text
: gettext('Slot'),
686 text
: gettext('Storage'),
687 xtype
: 'widgetcolumn',
690 xtype
: 'pveStorageSelector',
694 emptyText
: Proxmox
.Utils
.noneText
,
695 storageContent
: 'iso',
697 onWidgetAttach
: 'setNodename',
700 text
: gettext('ISO'),
702 xtype
: 'widgetcolumn',
705 xtype
: 'pveFileSelector',
709 emptyText
: Proxmox
.Utils
.noneText
,
710 storageContent
: 'iso',
712 onWidgetAttach
: 'setNodename',
717 xtype
: 'displayfield',
718 fieldLabel
: gettext('Network Interfaces'),
728 reference
: 'netGrid',
737 xtype
: 'checkcolumn',
738 header
: gettext('Use'),
742 checkchange: function(_column
, _rowIndex
, _checked
, record
) {
752 text
: gettext('MAC address'),
754 dataIndex
: 'macaddr',
755 renderer
: value
=> value
?? 'auto',
758 text
: gettext('Model'),
761 xtype
: 'widgetcolumn',
763 xtype
: 'pveNetworkCardSelector',
770 text
: gettext('Bridge'),
772 xtype
: 'widgetcolumn',
775 xtype
: 'PVE.form.BridgeSelector',
780 emptyText
: gettext('From Default'),
782 onWidgetAttach
: 'setNodename',
789 title
: gettext('Resulting Config'),
790 reference
: 'summaryTab',
794 reference
: 'summaryGrid',
805 { header
: 'Key', width
: 150, dataIndex
: 'key' },
806 { header
: 'Value', flex
: 1, dataIndex
: 'value' },
815 initComponent: function() {
818 if (!me
.volumeName
) {
819 throw "no volumeName given";
823 throw "no storage given";
827 throw "no nodename given";
832 me
.setTitle(Ext
.String
.format(gettext('Import Guest - {0}'), `${me.storage}:${me.volumeName}`));
834 me
.lookup('defaultStorage').setNodename(me
.nodename
);
835 me
.lookup('defaultBridge').setNodename(me
.nodename
);
836 me
.lookup('isoSelector').setNodename(me
.nodename
);
838 let renderWarning
= w
=> {
839 const warningsCatalogue
= {
840 'cdrom-image-ignored': gettext("CD-ROM images cannot get imported, if required you can reconfigure the '{0}' drive in the 'Advanced' tab."),
841 'nvme-unsupported': gettext("NVMe disks are currently not supported, '{0}' will get attaced as SCSI"),
842 'ovmf-with-lsi-unsupported': gettext("OVMF is built without LSI drivers, scsi hardware was set to '{1}'"),
843 'serial-port-socket-only': gettext("Serial socket '{0}' will be mapped to a socket"),
844 'guest-is-running': gettext('Virtual guest seems to be running on source host. Import might fail or have inconsistent state!'),
846 let message
= warningsCatalogue
[w
.type
];
847 if (!w
.type
|| !message
) {
848 return w
.message
?? w
.type
?? gettext('Unknown warning');
850 return Ext
.String
.format(message
, w
.key
?? 'unknown', w
.value
?? 'unknown');
854 success: function(response
) {
855 let data
= response
.result
.data
;
856 me
.vmConfig
= data
['create-args'];
859 for (const [id
, value
] of Object
.entries(data
.disks
?? {})) {
860 let volid
= Ext
.htmlEncode('<none>');
862 if (Ext
.isObject(value
)) {
870 'import-from': volid
,
876 for (const [id
, parsed
] of Object
.entries(data
.net
?? {})) {
878 parsed
.enable
= true;
883 for (const [id
, value
] of Object
.entries(me
.vmConfig
)) {
884 if (!Ext
.isString(value
) || !value
.match(/media=cdrom/)) {
891 delete me
.vmConfig
[id
];
893 me
.lookup('diskGrid').getStore().setData(disks
);
894 me
.lookup('netGrid').getStore().setData(nets
);
895 me
.lookup('cdGrid').getStore().setData(cdroms
);
897 me
.getViewModel().set('warnings', data
.warnings
.map(w
=> renderWarning(w
)));
899 let osinfo
= PVE
.Utils
.get_kvm_osinfo(me
.vmConfig
.ostype
?? '');