]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/window/GuestImport.js
ui: guest import: actually copy vmconfig state before applying values
[pve-manager.git] / www / manager6 / window / GuestImport.js
CommitLineData
bb3fa9de
DC
1Ext.define('PVE.window.GuestImport', {
2 extend: 'Proxmox.window.Edit', // fixme: Proxmox.window.Edit?
3 alias: 'widget.pveGuestImportWindow',
4
5 title: gettext('Import Guest'),
6
7 submitUrl: function() {
8 let me = this;
9 return `/nodes/${me.nodename}/qemu`;
10 },
11
12 isAdd: true,
13 isCreate: true,
14 submitText: gettext('Import'),
15 showTaskViewer: true,
16 method: 'POST',
17
18 loadUrl: function(_url, { storage, nodename, volumeName }) {
19 let args = Ext.Object.toQueryString({ volume: volumeName });
20 return `/nodes/${nodename}/storage/${storage}/import-metadata?${args}`;
21 },
22
23 controller: {
24 xclass: 'Ext.app.ViewController',
25
26 setNodename: function(_column, widget) {
27 let me = this;
28 let view = me.getView();
29 widget.setNodename(view.nodename);
30 },
31
b7583d45 32 diskStorageChange: function(storageSelector, value) {
bb3fa9de
DC
33 let me = this;
34
35 let grid = me.lookup('diskGrid');
36 let rec = storageSelector.getWidgetRecord();
b7583d45 37 let validFormats = storageSelector.store.getById(value)?.data.format;
bb3fa9de
DC
38 grid.query('pveDiskFormatSelector').some((selector) => {
39 if (selector.getWidgetRecord().data.id !== rec.data.id) {
40 return false;
41 }
42
43 if (validFormats?.[0]?.qcow2) {
44 selector.setDisabled(false);
45 selector.setValue('qcow2');
46 } else {
47 selector.setValue('raw');
48 selector.setDisabled(true);
49 }
50
51 return true;
52 });
53 },
54
b7583d45
DC
55 isoStorageChange: function(storageSelector, value) {
56 let me = this;
57
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) {
62 return false;
63 }
64
65 selector.setStorage(value);
66 if (!value) {
67 selector.setValue('');
68 }
69
70 return true;
71 });
72 },
73
4fb223f7
DC
74 onOSBaseChange: function(_field, value) {
75 let me = this;
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);
82 } else {
83 ostype.setValue(store.getAt(0));
84 }
85 },
86
c52eb476
DC
87 calculateConfig: function() {
88 let me = this;
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 })));
93 },
94
2d0bf566
DC
95 calculateAdditionalCDIdx: function() {
96 let me = this;
97
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)
101 if (maxIde < 0) {
102 return 'ide0';
103 }
104 if (maxIde < 2) {
105 return 'ide2';
106 }
107 if (maxSata < PVE.Utils.diskControllerMaxIDs.sata - 1) {
108 return `sata${maxSata+1}`;
109 }
110
111 return '';
112 },
113
d39e2492 114 // assume assigned sata disks indices are continuous, so without holes
2d0bf566 115 getMaxControllerId: function(controller) {
d39e2492
DC
116 let me = this;
117 let view = me.getView();
2d0bf566
DC
118 if (!controller) {
119 return -1;
120 }
121
122 let max = view[`max${controller}`];
123 if (max !== undefined) {
124 return max;
d39e2492
DC
125 }
126
2d0bf566 127 max = -1;
d39e2492 128 for (const key of Object.keys(me.getView().vmConfig)) {
2d0bf566 129 if (!key.toLowerCase().startsWith(controller)) {
d39e2492
DC
130 continue;
131 }
2d0bf566
DC
132 let idx = parseInt(key.slice(controller.length), 10);
133 if (idx > max) {
134 max = idx;
d39e2492
DC
135 }
136 }
137 me.lookup('diskGrid').getStore().each(rec => {
2d0bf566 138 if (!rec.data.id.toLowerCase().startsWith(controller)) {
d39e2492
DC
139 return;
140 }
2d0bf566
DC
141 let idx = parseInt(rec.data.id.slice(controller.length), 10);
142 if (idx > max) {
143 max = idx;
d39e2492
DC
144 }
145 });
146 me.lookup('cdGrid').getStore().each(rec => {
2d0bf566 147 if (!rec.data.id.toLowerCase().startsWith(controller) || rec.data.hidden) {
d39e2492
DC
148 return;
149 }
2d0bf566
DC
150 let idx = parseInt(rec.data.id.slice(controller.length), 10);
151 if (idx > max) {
152 max = idx;
d39e2492
DC
153 }
154 });
155
2d0bf566
DC
156 view[`max${controller}`] = max;
157 return max;
d39e2492
DC
158 },
159
160 mapDisk: function(value, metaData) {
161 let me = this;
c8c53aa4
TL
162 let prepareForVirtIO = me.lookup('prepareForVirtIO');
163 if (prepareForVirtIO.isDisabled() || !prepareForVirtIO.getValue()) {
d39e2492
DC
164 return value;
165 }
166 if (!value.toLowerCase().startsWith('scsi')) {
167 return value;
168 }
169 let offset = parseInt(value.slice(4), 10);
2d0bf566
DC
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
173 newIdx++;
174 }
175 if (newIdx >= PVE.Utils.diskControllerMaxIDs.sata) {
d39e2492
DC
176 let prefix = '';
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> `;
181 }
182 return `${prefix}${value}`;
183 }
184 return `sata${newIdx}`;
185 },
186
2d0bf566 187 refreshGrids: function() {
d39e2492 188 this.lookup('diskGrid').reconfigure();
2d0bf566 189 this.lookup('cdGrid').reconfigure();
d7155696 190 this.lookup('netGrid').reconfigure();
2d0bf566
DC
191 },
192
193 onOSTypeChange: function(_cb, value) {
194 let me = this;
195 if (!value) {
196 return;
197 }
198 let store = me.lookup('cdGrid').getStore();
199 let collection = store.getData().getSource() ?? store.getData();
200 let rec = collection.find('autogenerated', true);
201
ffa2f7b4 202 let isWindows = (value ?? '').startsWith('w');
2d0bf566
DC
203 if (rec) {
204 rec.set('hidden', !isWindows);
205 rec.commit();
206 }
c8c53aa4 207 let prepareVirtio = me.lookup('prepareForVirtIO').getValue();
6353e9f8
DC
208 let defaultScsiHw = me.getView().vmConfig.scsihw ?? '__default__';
209 me.lookup('scsihw').setValue(prepareVirtio && isWindows ? 'virtio-scsi-single' : defaultScsiHw);
2d0bf566
DC
210
211 me.refreshGrids();
d39e2492
DC
212 },
213
2d0bf566 214 onPrepareVirtioChange: function(_cb, value) {
b6ff1175 215 let me = this;
2d0bf566
DC
216
217 let scsihw = me.lookup('scsihw');
218 scsihw.suspendEvents();
219 scsihw.setValue(value ? 'virtio-scsi-single' : me.getView().vmConfig.scsihw);
220 scsihw.resumeEvents();
221
222 me.refreshGrids();
223 },
224
225 onScsiHwChange: function(_field, value) {
226 let me = this;
227 me.getView().vmConfig.scsihw = value;
b6ff1175
DC
228 },
229
2d101e45 230 onUniqueMACChange: function(_cb, value) {
d7155696 231 let me = this;
d7155696 232
2d101e45
TL
233 me.getViewModel().set('uniqueMACAdresses', value);
234
235 me.lookup('netGrid').reconfigure();
d7155696
AL
236 },
237
238 renderMacAddress: function(value, metaData, record, rowIndex, colIndex, store, view) {
239 let me = this;
2d101e45
TL
240 let vm = me.getViewModel();
241
242 return !vm.get('uniqueMACAdresses') && value ? value : 'auto';
d7155696
AL
243 },
244
bb3fa9de
DC
245 control: {
246 'grid field': {
247 // update records from widgetcolumns
248 change: function(widget, value) {
249 let rec = widget.getWidgetRecord();
250 rec.set(widget.name, value);
251 rec.commit();
252 },
253 },
b7583d45
DC
254 'grid[reference=diskGrid] pveStorageSelector': {
255 change: 'diskStorageChange',
256 },
257 'grid[reference=cdGrid] pveStorageSelector': {
258 change: 'isoStorageChange',
bb3fa9de 259 },
4fb223f7
DC
260 'field[name=osbase]': {
261 change: 'onOSBaseChange',
262 },
c52eb476
DC
263 'panel[reference=summaryTab]': {
264 activate: 'calculateConfig',
265 },
c8c53aa4 266 'proxmoxcheckbox[reference=prepareForVirtIO]': {
2d0bf566 267 change: 'onPrepareVirtioChange',
d39e2492
DC
268 },
269 'combobox[name=ostype]': {
2d0bf566 270 change: 'onOSTypeChange',
d39e2492 271 },
2d0bf566
DC
272 'pveScsiHwSelector': {
273 change: 'onScsiHwChange',
b6ff1175 274 },
2d101e45
TL
275 'proxmoxcheckbox[name=uniqueMACs]': {
276 change: 'onUniqueMACChange',
d7155696 277 },
bb3fa9de
DC
278 },
279 },
280
281 viewModel: {
282 data: {
283 coreCount: 1,
284 socketCount: 1,
226f01cb 285 liveImport: false,
0dca5cda 286 os: 'l26',
2d0bf566 287 maxCdDrives: false,
2d101e45 288 uniqueMACAdresses: false,
bb3fa9de
DC
289 warnings: [],
290 },
291
292 formulas: {
293 totalCoreCount: get => get('socketCount') * get('coreCount'),
294 hideWarnings: get => get('warnings').length === 0,
03d8d7d2
TL
295 warningsText: get => '<ul style="margin: 0; padding-left: 20px;">'
296 + get('warnings').map(w => `<li>${w}</li>`).join('') + '</ul>',
226f01cb
TL
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.'),
ffa2f7b4 299 isWindows: get => (get('os') ?? '').startsWith('w'),
bb3fa9de
DC
300 },
301 },
302
11236006 303 width: 700,
b0eeb862
DC
304 bodyPadding: 0,
305
911a62c5
TL
306 items: [{
307 xtype: 'tabpanel',
308 defaults: {
309 bodyPadding: 10,
310 },
311 items: [
312 {
313 title: gettext('General'),
314 xtype: 'inputpanel',
315 reference: 'mainInputPanel',
316 onGetValues: function(values) {
317 let me = this;
318 let grid = me.up('pveGuestImportWindow');
319 let vm = grid.getViewModel();
320
321 // from pveDiskStorageSelector
322 let defaultStorage = values.hdstorage;
323 let defaultFormat = values.diskformat;
324 delete values.hdstorage;
325 delete values.diskformat;
326
327 let defaultBridge = values.defaultBridge;
328 delete values.defaultBridge;
329
8f02fa43
TL
330 let config = { ...view.vmConfig };
331 Ext.apply(config, values);
911a62c5
TL
332
333 if (config.scsi0) {
334 config.scsi0 = config.scsi0.replace('local:0,', 'local:0,format=qcow2,');
335 }
336
337 let parsedBoot = PVE.Parser.parsePropertyString(config.boot ?? '');
338 if (parsedBoot.order) {
339 parsedBoot.order = parsedBoot.order.split(';');
340 }
341
342 grid.lookup('diskGrid').getStore().each((rec) => {
343 if (!rec.data.enable) {
344 return;
d39e2492 345 }
911a62c5
TL
346 let id = grid.getController().mapDisk(rec.data.id);
347 if (id !== rec.data.id && parsedBoot?.order) {
348 let idx = parsedBoot.order.indexOf(rec.data.id);
349 if (idx !== -1) {
350 parsedBoot.order[idx] = id;
b0eeb862 351 }
d39e2492 352 }
911a62c5
TL
353 let data = {
354 ...rec.data,
355 };
356 delete data.enable;
357 delete data.id;
358 delete data.size;
359 if (!data.file) {
360 data.file = defaultStorage;
361 data.format = defaultFormat;
362 }
363 data.file += ':0'; // for our special api format
364 if (id === 'efidisk0') {
365 delete data['import-from'];
366 }
367 config[id] = PVE.Parser.printQemuDrive(data);
368 });
d39e2492 369
911a62c5
TL
370 if (parsedBoot.order) {
371 parsedBoot.order = parsedBoot.order.join(';');
372 }
373 config.boot = PVE.Parser.printPropertyString(parsedBoot);
b0eeb862 374
911a62c5
TL
375 grid.lookup('netGrid').getStore().each((rec) => {
376 if (!rec.data.enable) {
377 return;
378 }
379 let id = rec.data.id;
380 let data = {
381 ...rec.data,
382 };
383 delete data.enable;
384 delete data.id;
385 if (!data.bridge) {
386 data.bridge = defaultBridge;
b0eeb862 387 }
911a62c5
TL
388 if (vm.get('uniqueMACAdresses')) {
389 data.macaddr = undefined;
390 }
391 config[id] = PVE.Parser.printQemuNetwork(data);
392 });
b0eeb862 393
911a62c5
TL
394 grid.lookup('cdGrid').getStore().each((rec) => {
395 if (!rec.data.enable) {
396 return;
8a0dd9d6 397 }
911a62c5
TL
398 let id = rec.data.id;
399 let cd = {
400 media: 'cdrom',
401 file: rec.data.file ? rec.data.file : 'none',
402 };
403 config[id] = PVE.Parser.printPropertyString(cd);
404 });
405
406 config.scsihw = grid.lookup('scsihw').getValue();
407
408 if (grid.lookup('liveimport').getValue()) {
409 config['live-restore'] = 1;
410 }
411
412 // remove __default__ values
413 for (const [key, value] of Object.entries(config)) {
414 if (value === '__default__') {
415 delete config[key];
416 }
417 }
8a0dd9d6 418
911a62c5
TL
419 return config;
420 },
bb3fa9de 421
911a62c5
TL
422 column1: [
423 {
424 xtype: 'pveGuestIDSelector',
425 name: 'vmid',
426 fieldLabel: 'VM',
427 guestType: 'qemu',
428 loadNextFreeID: true,
b0a9a170 429 validateExists: false,
911a62c5
TL
430 },
431 {
432 xtype: 'proxmoxintegerfield',
433 fieldLabel: gettext('Sockets'),
434 name: 'sockets',
435 reference: 'socketsField',
436 value: 1,
437 minValue: 1,
438 maxValue: 128,
439 allowBlank: true,
440 bind: {
441 value: '{socketCount}',
b0eeb862 442 },
911a62c5
TL
443 },
444 {
445 xtype: 'proxmoxintegerfield',
446 fieldLabel: gettext('Cores'),
447 name: 'cores',
448 reference: 'coresField',
449 value: 1,
450 minValue: 1,
451 maxValue: 1024,
452 allowBlank: true,
453 bind: {
454 value: '{coreCount}',
b0eeb862 455 },
911a62c5
TL
456 },
457 {
458 xtype: 'pveMemoryField',
459 fieldLabel: gettext('Memory') + ' (MiB)',
460 name: 'memory',
461 reference: 'memoryField',
462 value: 512,
463 allowBlank: true,
464 },
465 { xtype: 'displayfield' }, // spacer
466 { xtype: 'displayfield' }, // spacer
467 {
468 xtype: 'pveDiskStorageSelector',
469 reference: 'defaultStorage',
470 storageLabel: gettext('Default Storage'),
471 storageContent: 'images',
472 autoSelect: true,
473 hideSize: true,
474 name: 'defaultStorage',
475 },
476 ],
477
478 column2: [
479 {
480 xtype: 'textfield',
481 fieldLabel: gettext('Name'),
482 name: 'name',
483 vtype: 'DnsName',
484 reference: 'nameField',
485 allowBlank: true,
486 },
487 {
488 xtype: 'CPUModelSelector',
489 name: 'cpu',
490 reference: 'cputype',
491 value: 'x86-64-v2-AES',
492 fieldLabel: gettext('CPU Type'),
493 },
494 {
495 xtype: 'displayfield',
496 fieldLabel: gettext('Total cores'),
497 name: 'totalcores',
498 isFormField: false,
499 bind: {
500 value: '{totalCoreCount}',
b0eeb862 501 },
911a62c5
TL
502 },
503 {
504 xtype: 'combobox',
505 submitValue: false,
506 name: 'osbase',
507 fieldLabel: gettext('OS Type'),
508 editable: false,
509 queryMode: 'local',
510 value: 'Linux',
511 store: Object.keys(PVE.Utils.kvm_ostypes),
512 },
513 {
514 xtype: 'combobox',
515 name: 'ostype',
516 reference: 'ostype',
517 fieldLabel: gettext('Version'),
518 value: 'l26',
519 allowBlank: false,
520 editable: false,
521 queryMode: 'local',
522 valueField: 'val',
523 displayField: 'desc',
524 bind: {
525 value: '{os}',
b7583d45 526 },
911a62c5
TL
527 store: {
528 fields: ['desc', 'val'],
529 data: PVE.Utils.kvm_ostypes.Linux,
b7583d45 530 },
911a62c5
TL
531 },
532 { xtype: 'displayfield' }, // spacer
533 {
534 xtype: 'PVE.form.BridgeSelector',
535 reference: 'defaultBridge',
536 name: 'defaultBridge',
537 allowBlank: false,
538 fieldLabel: gettext('Default Bridge'),
539 },
540 ],
541
542 columnB: [
543 {
544 xtype: 'proxmoxcheckbox',
545 fieldLabel: gettext('Live Import'),
546 reference: 'liveimport',
547 isFormField: false,
548 boxLabelCls: 'pmx-hint black x-form-cb-label',
549 bind: {
550 value: '{liveImport}',
551 boxLabel: '{liveImportNote}',
1c2c2f69 552 },
911a62c5
TL
553 },
554 {
555 xtype: 'displayfield',
556 fieldLabel: gettext('Warnings'),
557 labelWidth: 200,
558 hidden: true,
559 bind: {
560 hidden: '{hideWarnings}',
b7583d45 561 },
911a62c5
TL
562 },
563 {
564 xtype: 'displayfield',
565 reference: 'warningText',
566 userCls: 'pmx-hint',
567 hidden: true,
568 bind: {
569 hidden: '{hideWarnings}',
570 value: '{warningsText}',
b7583d45 571 },
911a62c5
TL
572 },
573 ],
574 },
575 {
576 title: gettext('Advanced'),
577 xtype: 'inputpanel',
578
579 // the first inputpanel handles all values, so prevent value leakage here
580 onGetValues: () => ({}),
581
582 columnT: [
583 {
584 xtype: 'displayfield',
585 fieldLabel: gettext('Disks'),
586 labelWidth: 200,
587 },
588 {
589 xtype: 'grid',
590 reference: 'diskGrid',
591 minHeight: 60,
592 maxHeight: 150,
593 store: {
594 data: [],
595 sorters: [
596 'id',
597 ],
bb3fa9de 598 },
911a62c5
TL
599 columns: [
600 {
601 xtype: 'checkcolumn',
602 header: gettext('Use'),
603 width: 50,
604 dataIndex: 'enable',
605 listeners: {
606 checkchange: function(_column, _rowIndex, _checked, record) {
607 record.commit();
b0eeb862
DC
608 },
609 },
911a62c5
TL
610 },
611 {
612 text: gettext('Disk'),
613 dataIndex: 'id',
614 renderer: 'mapDisk',
615 },
616 {
617 text: gettext('Source'),
618 dataIndex: 'import-from',
619 flex: 1,
620 renderer: function(value) {
621 return value.replace(/^.*\//, '');
a5e56799 622 },
911a62c5
TL
623 },
624 {
625 text: gettext('Size'),
626 dataIndex: 'size',
627 renderer: (value) => {
628 if (Ext.isNumeric(value)) {
629 return Proxmox.Utils.render_size(value);
630 }
631 return value ?? Proxmox.Utils.unknownText;
b0eeb862 632 },
911a62c5
TL
633 },
634 {
635 text: gettext('Storage'),
636 dataIndex: 'file',
637 xtype: 'widgetcolumn',
638 width: 150,
639 widget: {
640 xtype: 'pveStorageSelector',
641 isFormField: false,
642 autoSelect: false,
643 allowBlank: true,
644 emptyText: gettext('From Default'),
645 name: 'file',
646 storageContent: 'images',
b0eeb862 647 },
911a62c5 648 onWidgetAttach: 'setNodename',
2d0bf566 649 },
911a62c5
TL
650 {
651 text: gettext('Format'),
652 dataIndex: 'format',
653 xtype: 'widgetcolumn',
654 width: 150,
655 widget: {
656 xtype: 'pveDiskFormatSelector',
657 name: 'format',
658 disabled: true,
659 isFormField: false,
660 matchFieldWidth: false,
661 },
2d0bf566 662 },
911a62c5
TL
663 ],
664 },
665 ],
666
667 column1: [
668 {
669 xtype: 'proxmoxcheckbox',
e4ebc552 670 boxLabel: gettext('Prepare for VirtIO-SCSI'),
c8c53aa4
TL
671 reference: 'prepareForVirtIO',
672 name: 'prepareForVirtIO',
911a62c5
TL
673 submitValue: false,
674 disabled: true,
675 bind: {
676 disabled: '{!isWindows}',
2d0bf566 677 },
911a62c5
TL
678 autoEl: {
679 tag: 'div',
680 'data-qtip': gettext('Maps SCSI disks to SATA and changes the SCSI Controller. Useful for a quicker switch to VirtIO-SCSI attached disks'),
2d0bf566 681 },
911a62c5
TL
682 },
683 ],
684
685 column2: [
686 {
687 xtype: 'pveScsiHwSelector',
688 reference: 'scsihw',
689 name: 'scsihw',
690 value: '__default__',
691 submitValue: false,
692 fieldLabel: gettext('SCSI Controller'),
693 },
694 ],
2d0bf566 695
911a62c5
TL
696 columnB: [
697 {
698 xtype: 'displayfield',
699 fieldLabel: gettext('CD/DVD Drives'),
700 labelWidth: 200,
701 },
702 {
703 xtype: 'grid',
704 reference: 'cdGrid',
705 minHeight: 60,
706 maxHeight: 150,
707 store: {
708 data: [],
709 sorters: [
710 'id',
711 ],
712 filters: [
713 function(rec) {
714 return !rec.data.hidden;
715 },
716 ],
bb3fa9de 717 },
911a62c5
TL
718 columns: [
719 {
720 xtype: 'checkcolumn',
721 header: gettext('Use'),
722 width: 50,
723 dataIndex: 'enable',
724 listeners: {
725 checkchange: function(_column, _rowIndex, _checked, record) {
726 record.commit();
b0eeb862
DC
727 },
728 },
911a62c5
TL
729 },
730 {
731 text: gettext('Slot'),
732 dataIndex: 'id',
733 sorted: true,
734 },
735 {
736 text: gettext('Storage'),
737 xtype: 'widgetcolumn',
738 width: 150,
739 widget: {
740 xtype: 'pveStorageSelector',
741 isFormField: false,
742 autoSelect: false,
743 allowBlank: true,
744 emptyText: Proxmox.Utils.noneText,
745 storageContent: 'iso',
b0eeb862 746 },
911a62c5
TL
747 onWidgetAttach: 'setNodename',
748 },
749 {
750 text: gettext('ISO'),
751 dataIndex: 'file',
752 xtype: 'widgetcolumn',
753 flex: 1,
754 widget: {
755 xtype: 'pveFileSelector',
756 name: 'file',
757 isFormField: false,
758 allowBlank: true,
759 emptyText: Proxmox.Utils.noneText,
760 storageContent: 'iso',
b0eeb862 761 },
911a62c5 762 onWidgetAttach: 'setNodename',
163868e1 763 },
911a62c5
TL
764 ],
765 },
766 {
767 xtype: 'displayfield',
768 fieldLabel: gettext('Network Interfaces'),
769 labelWidth: 200,
770 style: {
771 paddingTop: '10px',
bb3fa9de 772 },
911a62c5
TL
773 },
774 {
775 xtype: 'grid',
776 minHeight: 58,
777 maxHeight: 150,
778 reference: 'netGrid',
779 store: {
780 data: [],
781 sorters: [
782 'id',
783 ],
784 },
785 columns: [
786 {
787 xtype: 'checkcolumn',
788 header: gettext('Use'),
789 width: 50,
790 dataIndex: 'enable',
791 listeners: {
792 checkchange: function(_column, _rowIndex, _checked, record) {
793 record.commit();
b0eeb862
DC
794 },
795 },
911a62c5
TL
796 },
797 {
798 text: gettext('ID'),
799 dataIndex: 'id',
800 },
801 {
802 text: gettext('MAC address'),
803 flex: 1,
804 dataIndex: 'macaddr',
805 renderer: 'renderMacAddress',
806 },
807 {
808 text: gettext('Model'),
809 flex: 1,
810 dataIndex: 'model',
811 xtype: 'widgetcolumn',
812 widget: {
813 xtype: 'pveNetworkCardSelector',
814 name: 'model',
815 isFormField: false,
816 allowBlank: false,
b0eeb862 817 },
911a62c5
TL
818 },
819 {
820 text: gettext('Bridge'),
821 dataIndex: 'bridge',
822 xtype: 'widgetcolumn',
823 flex: 1,
824 widget: {
825 xtype: 'PVE.form.BridgeSelector',
826 name: 'bridge',
827 isFormField: false,
828 autoSelect: false,
829 allowBlank: true,
830 emptyText: gettext('From Default'),
b0eeb862 831 },
911a62c5 832 onWidgetAttach: 'setNodename',
c52eb476 833 },
911a62c5
TL
834 ],
835 },
836 {
837 xtype: 'proxmoxcheckbox',
838 name: 'uniqueMACs',
839 boxLabel: gettext('Unique MAC addresses'),
840 uncheckedValue: false,
841 value: false,
842 },
843 ],
844 },
845 {
846 title: gettext('Resulting Config'),
847 reference: 'summaryTab',
848 items: [
849 {
850 xtype: 'grid',
851 reference: 'summaryGrid',
852 maxHeight: 400,
853 scrollable: true,
854 store: {
855 model: 'KeyValue',
856 sorters: [{
857 property: 'key',
858 direction: 'ASC',
859 }],
c52eb476 860 },
911a62c5
TL
861 columns: [
862 { header: 'Key', width: 150, dataIndex: 'key' },
863 { header: 'Value', flex: 1, dataIndex: 'value' },
864 ],
865 },
866 ],
867 },
868 ],
869 }],
bb3fa9de
DC
870
871 initComponent: function() {
872 let me = this;
873
874 if (!me.volumeName) {
875 throw "no volumeName given";
876 }
877
878 if (!me.storage) {
879 throw "no storage given";
880 }
881
882 if (!me.nodename) {
883 throw "no nodename given";
884 }
885
886 me.callParent();
887
bb3fa9de
DC
888 me.setTitle(Ext.String.format(gettext('Import Guest - {0}'), `${me.storage}:${me.volumeName}`));
889
b0eeb862
DC
890 me.lookup('defaultStorage').setNodename(me.nodename);
891 me.lookup('defaultBridge').setNodename(me.nodename);
892
03d8d7d2
TL
893 let renderWarning = w => {
894 const warningsCatalogue = {
463b9b82 895 'cdrom-image-ignored': gettext("CD-ROM images cannot get imported, if required you can reconfigure the '{0}' drive in the 'Advanced' tab."),
03d8d7d2
TL
896 'nvme-unsupported': gettext("NVMe disks are currently not supported, '{0}' will get attaced as SCSI"),
897 'ovmf-with-lsi-unsupported': gettext("OVMF is built without LSI drivers, scsi hardware was set to '{1}'"),
898 'serial-port-socket-only': gettext("Serial socket '{0}' will be mapped to a socket"),
b185bbad 899 'guest-is-running': gettext('Virtual guest seems to be running on source host. Import might fail or have inconsistent state!'),
24d60df3
DC
900 'efi-state-lost': Ext.String.format(
901 gettext('EFI state cannot be imported, you may need to reconfigure the boot order (see {0})'),
902 '<a href="https://pve.proxmox.com/wiki/OVMF/UEFI_Boot_Entries">OVMF/UEFI Boot Entries</a>',
903 ),
03d8d7d2
TL
904 };
905 let message = warningsCatalogue[w.type];
906 if (!w.type || !message) {
907 return w.message ?? w.type ?? gettext('Unknown warning');
908 }
909 return Ext.String.format(message, w.key ?? 'unknown', w.value ?? 'unknown');
910 };
911
bb3fa9de
DC
912 me.load({
913 success: function(response) {
914 let data = response.result.data;
915 me.vmConfig = data['create-args'];
916
917 let disks = [];
918 for (const [id, value] of Object.entries(data.disks ?? {})) {
a5e56799
DC
919 let volid = Ext.htmlEncode('<none>');
920 let size = 'auto';
921 if (Ext.isObject(value)) {
922 volid = value.volid;
923 size = value.size;
924 }
bb3fa9de
DC
925 disks.push({
926 id,
927 enable: true,
a5e56799
DC
928 size,
929 'import-from': volid,
bb3fa9de
DC
930 format: 'raw',
931 });
932 }
933
934 let nets = [];
935 for (const [id, parsed] of Object.entries(data.net ?? {})) {
936 parsed.id = id;
937 parsed.enable = true;
938 nets.push(parsed);
939 }
b7583d45
DC
940
941 let cdroms = [];
942 for (const [id, value] of Object.entries(me.vmConfig)) {
943 if (!Ext.isString(value) || !value.match(/media=cdrom/)) {
944 continue;
945 }
946 cdroms.push({
947 enable: true,
2d0bf566 948 hidden: false,
b7583d45
DC
949 id,
950 });
951 delete me.vmConfig[id];
952 }
2d0bf566 953
bb3fa9de
DC
954 me.lookup('diskGrid').getStore().setData(disks);
955 me.lookup('netGrid').getStore().setData(nets);
b7583d45 956 me.lookup('cdGrid').getStore().setData(cdroms);
bb3fa9de 957
2d0bf566
DC
958 let additionalCdIdx = me.getController().calculateAdditionalCDIdx();
959 if (additionalCdIdx === '') {
960 me.getViewModel().set('maxCdDrives', true);
961 } else if (cdroms.length === 0) {
962 me.additionalCdIdx = additionalCdIdx;
963 me.lookup('cdGrid').getStore().add({
964 enable: true,
ffa2f7b4 965 hidden: !(me.vmConfig.ostype ?? '').startsWith('w'),
2d0bf566
DC
966 id: additionalCdIdx,
967 autogenerated: true,
968 });
969 }
970
03d8d7d2 971 me.getViewModel().set('warnings', data.warnings.map(w => renderWarning(w)));
bb3fa9de 972
4fb223f7 973 let osinfo = PVE.Utils.get_kvm_osinfo(me.vmConfig.ostype ?? '');
c8c53aa4 974 let prepareForVirtIO = (me.vmConfig.ostype ?? '').startsWith('w') && (me.vmConfig.bios ?? '').indexOf('ovmf') !== -1;
4fb223f7
DC
975
976 me.setValues({
977 osbase: osinfo.base,
978 ...me.vmConfig,
979 });
4a855db6
DC
980
981
c8c53aa4 982 me.lookup('prepareForVirtIO').setValue(prepareForVirtIO);
bb3fa9de
DC
983 },
984 });
985 },
986});