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