]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/window/GuestImport.js
ui: guest import: add cdrom grid
[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
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
b7583d45
DC
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
bb3fa9de
DC
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 },
4fb223f7
DC
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 },
bb3fa9de
DC
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: { data: [] },
288 columns: [
289 {
290 xtype: 'checkcolumn',
291 header: gettext('Use'),
292 width: 50,
293 dataIndex: 'enable',
294 listeners: {
295 checkchange: function(_column, _rowIndex, _checked, record) {
296 record.commit();
297 },
298 },
299 },
300 {
301 text: gettext('Disk'),
302 dataIndex: 'id',
303 },
304 {
305 text: gettext('Source'),
306 dataIndex: 'import-from',
307 flex: 1,
308 renderer: function(value) {
309 return value.replace(/^.*\//, '');
310 },
311 },
312 {
313 text: gettext('Storage'),
314 dataIndex: 'file',
315 xtype: 'widgetcolumn',
316 width: 150,
317 widget: {
318 xtype: 'pveStorageSelector',
319 isFormField: false,
320 name: 'file',
321 storageContent: 'images',
322 },
323 onWidgetAttach: 'setNodename',
324 },
325 {
326 text: gettext('Format'),
327 dataIndex: 'format',
328 xtype: 'widgetcolumn',
329 width: 150,
330 widget: {
331 xtype: 'pveDiskFormatSelector',
332 name: 'format',
333 isFormField: false,
334 matchFieldWidth: false,
335 },
336 },
337 ],
338 },
b7583d45
DC
339 {
340 xtype: 'displayfield',
341 fieldLabel: gettext('CD/DVD Drives'),
342 labelWidth: 200,
343 },
344 {
345 xtype: 'grid',
346 reference: 'cdGrid',
347 maxHeight: 150,
348 store: {
349 data: [],
350 sorters: [
351 'id',
352 ],
353 },
354 columns: [
355 {
356 xtype: 'checkcolumn',
357 header: gettext('Use'),
358 width: 50,
359 dataIndex: 'enable',
360 listeners: {
361 checkchange: function(_column, _rowIndex, _checked, record) {
362 record.commit();
363 },
364 },
365 },
366 {
367 text: gettext('Slot'),
368 dataIndex: 'id',
369 sorted: true,
370 },
371 {
372 text: gettext('Storage'),
373 xtype: 'widgetcolumn',
374 width: 150,
375 widget: {
376 xtype: 'pveStorageSelector',
377 isFormField: false,
378 autoSelect: false,
379 allowBlank: true,
380 emptyText: Proxmox.Utils.noneText,
381 storageContent: 'iso',
382 },
383 onWidgetAttach: 'setNodename',
384 },
385 {
386 text: gettext('ISO'),
387 dataIndex: 'file',
388 xtype: 'widgetcolumn',
389 flex: 1,
390 widget: {
391 xtype: 'pveFileSelector',
392 name: 'file',
393 isFormField: false,
394 allowBlank: true,
395 emptyText: Proxmox.Utils.noneText,
396 storageContent: 'iso',
397 },
398 onWidgetAttach: 'setNodename',
399 },
400 ],
401 },
bb3fa9de
DC
402 {
403 xtype: 'displayfield',
404 fieldLabel: gettext('Network Interfaces'),
405 labelWidth: 200,
406 },
407 {
408 xtype: 'grid',
409 maxHeight: 150,
410 reference: 'netGrid',
411 store: { data: [] },
412 columns: [
413 {
414 xtype: 'checkcolumn',
415 header: gettext('Use'),
416 width: 50,
417 dataIndex: 'enable',
418 listeners: {
419 checkchange: function(_column, _rowIndex, _checked, record) {
420 record.commit();
421 },
422 },
423 },
424 {
425 text: gettext('ID'),
426 dataIndex: 'id',
427 },
428 {
429 text: gettext('MAC address'),
430 flex: 1,
431 dataIndex: 'macaddr',
432 },
433 {
434 text: gettext('Model'),
435 flex: 1,
436 dataIndex: 'model',
437 },
438 {
439 text: gettext('Bridge'),
440 dataIndex: 'bridge',
441 xtype: 'widgetcolumn',
442 widget: {
443 xtype: 'PVE.form.BridgeSelector',
444 name: 'bridge',
445 isFormField: false,
446 allowBlank: false,
447 },
448 onWidgetAttach: 'setNodename',
449 },
450 ],
451 },
452 {
453 xtype: 'displayfield',
454 fieldLabel: gettext('Warnings'),
455 labelWidth: 200,
456 hidden: true,
457 bind: {
458 hidden: '{hideWarnings}',
459 },
460 },
461 {
462 xtype: 'displayfield',
463 reference: 'warningText',
464 userCls: 'pmx-hint',
465 hidden: true,
466 bind: {
467 hidden: '{hideWarnings}',
468 value: '{warningsText}',
469 },
470 },
471 ],
472 },
473 ],
474
475 initComponent: function() {
476 let me = this;
477
478 if (!me.volumeName) {
479 throw "no volumeName given";
480 }
481
482 if (!me.storage) {
483 throw "no storage given";
484 }
485
486 if (!me.nodename) {
487 throw "no nodename given";
488 }
489
490 me.callParent();
491
492 me.query('toolbar')?.[0]?.insert(0, {
493 xtype: 'proxmoxcheckbox',
494 reference: 'liveimport',
495 boxLabelAlign: 'before',
496 boxLabel: gettext('Live Import'),
497 });
498
499 me.setTitle(Ext.String.format(gettext('Import Guest - {0}'), `${me.storage}:${me.volumeName}`));
500
03d8d7d2
TL
501 let renderWarning = w => {
502 const warningsCatalogue = {
503 'cdrom-image-ignored': gettext("CD-ROM images cannot get imported, please reconfigure the '{0}' drive after the import"),
504 'nvme-unsupported': gettext("NVMe disks are currently not supported, '{0}' will get attaced as SCSI"),
505 'ovmf-with-lsi-unsupported': gettext("OVMF is built without LSI drivers, scsi hardware was set to '{1}'"),
506 'serial-port-socket-only': gettext("Serial socket '{0}' will be mapped to a socket"),
507 };
508 let message = warningsCatalogue[w.type];
509 if (!w.type || !message) {
510 return w.message ?? w.type ?? gettext('Unknown warning');
511 }
512 return Ext.String.format(message, w.key ?? 'unknown', w.value ?? 'unknown');
513 };
514
bb3fa9de
DC
515 me.load({
516 success: function(response) {
517 let data = response.result.data;
518 me.vmConfig = data['create-args'];
519
520 let disks = [];
521 for (const [id, value] of Object.entries(data.disks ?? {})) {
522 disks.push({
523 id,
524 enable: true,
525 'import-from': id === 'efidisk0' ? Ext.htmlEncode('<none>') : value,
526 format: 'raw',
527 });
528 }
529
530 let nets = [];
531 for (const [id, parsed] of Object.entries(data.net ?? {})) {
532 parsed.id = id;
533 parsed.enable = true;
534 nets.push(parsed);
535 }
b7583d45
DC
536
537 let cdroms = [];
538 for (const [id, value] of Object.entries(me.vmConfig)) {
539 if (!Ext.isString(value) || !value.match(/media=cdrom/)) {
540 continue;
541 }
542 cdroms.push({
543 enable: true,
544 id,
545 });
546 delete me.vmConfig[id];
547 }
bb3fa9de
DC
548 me.lookup('diskGrid').getStore().setData(disks);
549 me.lookup('netGrid').getStore().setData(nets);
b7583d45 550 me.lookup('cdGrid').getStore().setData(cdroms);
bb3fa9de 551
03d8d7d2 552 me.getViewModel().set('warnings', data.warnings.map(w => renderWarning(w)));
bb3fa9de 553
4fb223f7
DC
554 let osinfo = PVE.Utils.get_kvm_osinfo(me.vmConfig.ostype ?? '');
555
556 me.setValues({
557 osbase: osinfo.base,
558 ...me.vmConfig,
559 });
bb3fa9de
DC
560 },
561 });
562 },
563});