]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/window/GuestImport.js
ui: guest import: make window wider
[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
bb3fa9de
DC
87 control: {
88 'grid field': {
89 // update records from widgetcolumns
90 change: function(widget, value) {
91 let rec = widget.getWidgetRecord();
92 rec.set(widget.name, value);
93 rec.commit();
94 },
95 },
b7583d45
DC
96 'grid[reference=diskGrid] pveStorageSelector': {
97 change: 'diskStorageChange',
98 },
99 'grid[reference=cdGrid] pveStorageSelector': {
100 change: 'isoStorageChange',
bb3fa9de 101 },
4fb223f7
DC
102 'field[name=osbase]': {
103 change: 'onOSBaseChange',
104 },
bb3fa9de
DC
105 },
106 },
107
108 viewModel: {
109 data: {
110 coreCount: 1,
111 socketCount: 1,
112 warnings: [],
113 },
114
115 formulas: {
116 totalCoreCount: get => get('socketCount') * get('coreCount'),
117 hideWarnings: get => get('warnings').length === 0,
03d8d7d2
TL
118 warningsText: get => '<ul style="margin: 0; padding-left: 20px;">'
119 + get('warnings').map(w => `<li>${w}</li>`).join('') + '</ul>',
bb3fa9de
DC
120 },
121 },
122
11236006 123 width: 700,
b0eeb862
DC
124 bodyPadding: 0,
125
bb3fa9de
DC
126 items: [
127 {
b0eeb862
DC
128 xtype: 'tabpanel',
129 defaults: {
130 bodyPadding: 10,
bb3fa9de 131 },
b0eeb862 132 items: [
bb3fa9de 133 {
b0eeb862
DC
134 title: gettext('General'),
135 xtype: 'inputpanel',
136 reference: 'mainInputPanel',
137 onGetValues: function(values) {
138 let me = this;
139 let grid = me.up('pveGuestImportWindow');
140
141 // from pveDiskStorageSelector
142 let defaultStorage = values.hdstorage;
143 let defaultFormat = values.diskformat;
144 delete values.hdstorage;
145 delete values.diskformat;
146
147 let defaultBridge = values.defaultBridge;
148 delete values.defaultBridge;
149
150 let config = Ext.apply(grid.vmConfig, values);
151
152 if (config.scsi0) {
153 config.scsi0 = config.scsi0.replace('local:0,', 'local:0,format=qcow2,');
154 }
155
156 grid.lookup('diskGrid').getStore().each((rec) => {
157 if (!rec.data.enable) {
158 return;
159 }
160 let id = rec.data.id;
161 let data = {
162 ...rec.data,
163 };
164 delete data.enable;
165 delete data.id;
166 if (!data.file) {
167 data.file = defaultStorage;
168 data.format = defaultFormat;
169 }
170 data.file += ':0'; // for our special api format
171 if (id === 'efidisk0') {
172 delete data['import-from'];
173 }
174 config[id] = PVE.Parser.printQemuDrive(data);
175 });
176
177 grid.lookup('netGrid').getStore().each((rec) => {
178 if (!rec.data.enable) {
179 return;
180 }
181 let id = rec.data.id;
182 let data = {
183 ...rec.data,
184 };
185 delete data.enable;
186 delete data.id;
187 if (!data.bridge) {
188 data.bridge = defaultBridge;
189 }
190 config[id] = PVE.Parser.printQemuNetwork(data);
191 });
192
193 grid.lookup('cdGrid').getStore().each((rec) => {
194 if (!rec.data.enable) {
195 return;
196 }
197 let id = rec.data.id;
198 let cd = {
199 media: 'cdrom',
200 file: rec.data.file ? rec.data.file : 'none',
201 };
202 config[id] = PVE.Parser.printPropertyString(cd);
203 });
204
205 if (grid.lookup('liveimport').getValue()) {
206 config['live-restore'] = 1;
207 }
208
209 return config;
bb3fa9de 210 },
bb3fa9de 211
b0eeb862 212 column1: [
bb3fa9de 213 {
b0eeb862
DC
214 xtype: 'pveGuestIDSelector',
215 name: 'vmid',
216 fieldLabel: 'VM',
217 guestType: 'qemu',
218 loadNextFreeID: true,
bb3fa9de
DC
219 },
220 {
b0eeb862
DC
221 xtype: 'proxmoxintegerfield',
222 fieldLabel: gettext('Sockets'),
223 name: 'sockets',
224 reference: 'socketsField',
225 value: 1,
226 minValue: 1,
227 maxValue: 4,
228 allowBlank: true,
229 bind: {
230 value: '{socketCount}',
231 },
bb3fa9de
DC
232 },
233 {
b0eeb862
DC
234 xtype: 'proxmoxintegerfield',
235 fieldLabel: gettext('Cores'),
236 name: 'cores',
237 reference: 'coresField',
238 value: 1,
239 minValue: 1,
240 maxValue: 128,
241 allowBlank: true,
242 bind: {
243 value: '{coreCount}',
bb3fa9de
DC
244 },
245 },
246 {
b0eeb862
DC
247 xtype: 'pveMemoryField',
248 fieldLabel: gettext('Memory'),
249 name: 'memory',
250 reference: 'memoryField',
251 value: 512,
252 allowBlank: true,
bb3fa9de
DC
253 },
254 {
b0eeb862
DC
255 //spacer
256 xtype: 'displayfield',
257 },
258 {
259 xtype: 'pveDiskStorageSelector',
260 reference: 'defaultStorage',
261 storageLabel: gettext('Default Storage'),
262 storageContent: 'images',
263 autoSelect: true,
264 hideSize: true,
265 name: 'defaultStorage',
bb3fa9de
DC
266 },
267 ],
b0eeb862
DC
268
269 column2: [
b7583d45 270 {
b0eeb862
DC
271 xtype: 'textfield',
272 fieldLabel: gettext('Name'),
273 name: 'name',
274 vtype: 'DnsName',
275 reference: 'nameField',
276 allowBlank: true,
277 },
278 {
279 xtype: 'CPUModelSelector',
280 name: 'cpu',
281 reference: 'cputype',
282 value: 'x86-64-v2-AES',
283 fieldLabel: gettext('Type'),
284 },
285 {
286 xtype: 'displayfield',
287 fieldLabel: gettext('Total cores'),
288 name: 'totalcores',
289 isFormField: false,
290 bind: {
291 value: '{totalCoreCount}',
292 },
293 },
294 {
295 xtype: 'combobox',
296 submitValue: false,
297 name: 'osbase',
298 fieldLabel: gettext('OS Type'),
299 editable: false,
300 queryMode: 'local',
301 value: 'Linux',
302 store: Object.keys(PVE.Utils.kvm_ostypes),
303 },
304 {
305 xtype: 'combobox',
306 name: 'ostype',
307 reference: 'ostype',
308 fieldLabel: gettext('Version'),
309 value: 'l26',
310 allowBlank: false,
311 editable: false,
312 queryMode: 'local',
313 valueField: 'val',
314 displayField: 'desc',
315 store: {
316 fields: ['desc', 'val'],
317 data: PVE.Utils.kvm_ostypes.Linux,
b7583d45
DC
318 },
319 },
320 {
b0eeb862
DC
321 xtype: 'PVE.form.BridgeSelector',
322 reference: 'defaultBridge',
323 name: 'defaultBridge',
324 allowBlank: false,
325 fieldLabel: gettext('Default Bridge'),
b7583d45 326 },
b0eeb862
DC
327 ],
328
329 columnB: [
b7583d45 330 {
b0eeb862
DC
331 xtype: 'displayfield',
332 fieldLabel: gettext('Warnings'),
333 labelWidth: 200,
334 hidden: true,
335 bind: {
336 hidden: '{hideWarnings}',
b7583d45 337 },
b7583d45
DC
338 },
339 {
b0eeb862
DC
340 xtype: 'displayfield',
341 reference: 'warningText',
342 userCls: 'pmx-hint',
343 hidden: true,
344 bind: {
345 hidden: '{hideWarnings}',
346 value: '{warningsText}',
b7583d45 347 },
b7583d45
DC
348 },
349 ],
350 },
bb3fa9de 351 {
b0eeb862
DC
352 title: gettext('Advanced'),
353 xtype: 'inputpanel',
354 items: [
bb3fa9de 355 {
b0eeb862
DC
356 xtype: 'displayfield',
357 fieldLabel: gettext('Disks'),
358 labelWidth: 200,
bb3fa9de
DC
359 },
360 {
b0eeb862
DC
361 xtype: 'grid',
362 reference: 'diskGrid',
363 minHeight: 58,
364 maxHeight: 150,
365 store: {
366 data: [],
367 sorters: [
368 'id',
369 ],
370 },
371 columns: [
372 {
373 xtype: 'checkcolumn',
374 header: gettext('Use'),
375 width: 50,
376 dataIndex: 'enable',
377 listeners: {
378 checkchange: function(_column, _rowIndex, _checked, record) {
379 record.commit();
380 },
381 },
382 },
383 {
384 text: gettext('Disk'),
385 dataIndex: 'id',
386 },
387 {
388 text: gettext('Source'),
389 dataIndex: 'import-from',
390 flex: 1,
391 renderer: function(value) {
392 return value.replace(/^.*\//, '');
393 },
394 },
395 {
396 text: gettext('Storage'),
397 dataIndex: 'file',
398 xtype: 'widgetcolumn',
399 width: 150,
400 widget: {
401 xtype: 'pveStorageSelector',
402 isFormField: false,
403 autoSelect: false,
404 allowBlank: true,
405 emptyText: gettext('From Default'),
406 name: 'file',
407 storageContent: 'images',
408 },
409 onWidgetAttach: 'setNodename',
410 },
411 {
412 text: gettext('Format'),
413 dataIndex: 'format',
414 xtype: 'widgetcolumn',
415 width: 150,
416 widget: {
417 xtype: 'pveDiskFormatSelector',
418 name: 'format',
419 disabled: true,
420 isFormField: false,
421 matchFieldWidth: false,
422 },
423 },
424 ],
bb3fa9de
DC
425 },
426 {
b0eeb862
DC
427 xtype: 'displayfield',
428 fieldLabel: gettext('CD/DVD Drives'),
429 labelWidth: 200,
bb3fa9de
DC
430 },
431 {
b0eeb862
DC
432 xtype: 'grid',
433 reference: 'cdGrid',
434 minHeight: 58,
435 maxHeight: 150,
436 store: {
437 data: [],
438 sorters: [
439 'id',
440 ],
81c46efc 441 },
b0eeb862
DC
442 columns: [
443 {
444 xtype: 'checkcolumn',
445 header: gettext('Use'),
446 width: 50,
447 dataIndex: 'enable',
448 listeners: {
449 checkchange: function(_column, _rowIndex, _checked, record) {
450 record.commit();
451 },
452 },
453 },
454 {
455 text: gettext('Slot'),
456 dataIndex: 'id',
457 sorted: true,
458 },
459 {
460 text: gettext('Storage'),
461 xtype: 'widgetcolumn',
462 width: 150,
463 widget: {
464 xtype: 'pveStorageSelector',
465 isFormField: false,
466 autoSelect: false,
467 allowBlank: true,
468 emptyText: Proxmox.Utils.noneText,
469 storageContent: 'iso',
470 },
471 onWidgetAttach: 'setNodename',
472 },
473 {
474 text: gettext('ISO'),
475 dataIndex: 'file',
476 xtype: 'widgetcolumn',
477 flex: 1,
478 widget: {
479 xtype: 'pveFileSelector',
480 name: 'file',
481 isFormField: false,
482 allowBlank: true,
483 emptyText: Proxmox.Utils.noneText,
484 storageContent: 'iso',
485 },
486 onWidgetAttach: 'setNodename',
487 },
488 ],
489 },
490 {
491 xtype: 'displayfield',
492 fieldLabel: gettext('Network Interfaces'),
493 labelWidth: 200,
bb3fa9de
DC
494 },
495 {
b0eeb862
DC
496 xtype: 'grid',
497 minHeight: 58,
498 maxHeight: 150,
499 reference: 'netGrid',
500 store: {
501 data: [],
502 sorters: [
503 'id',
504 ],
bb3fa9de 505 },
b0eeb862
DC
506 columns: [
507 {
508 xtype: 'checkcolumn',
509 header: gettext('Use'),
510 width: 50,
511 dataIndex: 'enable',
512 listeners: {
513 checkchange: function(_column, _rowIndex, _checked, record) {
514 record.commit();
515 },
516 },
517 },
518 {
519 text: gettext('ID'),
520 dataIndex: 'id',
521 },
522 {
523 text: gettext('MAC address'),
524 flex: 1,
525 dataIndex: 'macaddr',
526 renderer: value => value ?? 'auto',
527 },
528 {
529 text: gettext('Model'),
530 flex: 1,
531 dataIndex: 'model',
532 xtype: 'widgetcolumn',
533 widget: {
534 xtype: 'pveNetworkCardSelector',
535 name: 'model',
536 isFormField: false,
537 allowBlank: false,
538 },
539 },
540 {
541 text: gettext('Bridge'),
542 dataIndex: 'bridge',
543 xtype: 'widgetcolumn',
544 flex: 1,
545 widget: {
546 xtype: 'PVE.form.BridgeSelector',
547 name: 'bridge',
548 isFormField: false,
549 autoSelect: false,
550 allowBlank: true,
551 emptyText: gettext('From Default'),
552 },
553 onWidgetAttach: 'setNodename',
554 },
555 ],
bb3fa9de
DC
556 },
557 ],
558 },
bb3fa9de
DC
559 ],
560 },
561 ],
562
563 initComponent: function() {
564 let me = this;
565
566 if (!me.volumeName) {
567 throw "no volumeName given";
568 }
569
570 if (!me.storage) {
571 throw "no storage given";
572 }
573
574 if (!me.nodename) {
575 throw "no nodename given";
576 }
577
578 me.callParent();
579
580 me.query('toolbar')?.[0]?.insert(0, {
581 xtype: 'proxmoxcheckbox',
582 reference: 'liveimport',
583 boxLabelAlign: 'before',
584 boxLabel: gettext('Live Import'),
585 });
586
587 me.setTitle(Ext.String.format(gettext('Import Guest - {0}'), `${me.storage}:${me.volumeName}`));
588
b0eeb862
DC
589 me.lookup('defaultStorage').setNodename(me.nodename);
590 me.lookup('defaultBridge').setNodename(me.nodename);
591
03d8d7d2
TL
592 let renderWarning = w => {
593 const warningsCatalogue = {
594 'cdrom-image-ignored': gettext("CD-ROM images cannot get imported, please reconfigure the '{0}' drive after the import"),
595 'nvme-unsupported': gettext("NVMe disks are currently not supported, '{0}' will get attaced as SCSI"),
596 'ovmf-with-lsi-unsupported': gettext("OVMF is built without LSI drivers, scsi hardware was set to '{1}'"),
597 'serial-port-socket-only': gettext("Serial socket '{0}' will be mapped to a socket"),
598 };
599 let message = warningsCatalogue[w.type];
600 if (!w.type || !message) {
601 return w.message ?? w.type ?? gettext('Unknown warning');
602 }
603 return Ext.String.format(message, w.key ?? 'unknown', w.value ?? 'unknown');
604 };
605
bb3fa9de
DC
606 me.load({
607 success: function(response) {
608 let data = response.result.data;
609 me.vmConfig = data['create-args'];
610
611 let disks = [];
612 for (const [id, value] of Object.entries(data.disks ?? {})) {
613 disks.push({
614 id,
615 enable: true,
616 'import-from': id === 'efidisk0' ? Ext.htmlEncode('<none>') : value,
617 format: 'raw',
618 });
619 }
620
621 let nets = [];
622 for (const [id, parsed] of Object.entries(data.net ?? {})) {
623 parsed.id = id;
624 parsed.enable = true;
625 nets.push(parsed);
626 }
b7583d45
DC
627
628 let cdroms = [];
629 for (const [id, value] of Object.entries(me.vmConfig)) {
630 if (!Ext.isString(value) || !value.match(/media=cdrom/)) {
631 continue;
632 }
633 cdroms.push({
634 enable: true,
635 id,
636 });
637 delete me.vmConfig[id];
638 }
bb3fa9de
DC
639 me.lookup('diskGrid').getStore().setData(disks);
640 me.lookup('netGrid').getStore().setData(nets);
b7583d45 641 me.lookup('cdGrid').getStore().setData(cdroms);
bb3fa9de 642
03d8d7d2 643 me.getViewModel().set('warnings', data.warnings.map(w => renderWarning(w)));
bb3fa9de 644
4fb223f7
DC
645 let osinfo = PVE.Utils.get_kvm_osinfo(me.vmConfig.ostype ?? '');
646
647 me.setValues({
648 osbase: osinfo.base,
649 ...me.vmConfig,
650 });
bb3fa9de
DC
651 },
652 });
653 },
654});