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