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