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