]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/window/GuestImport.js
70cdb75f49edebc504f7dd51c7542c8fc40f9969
[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 storageChange: 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 control: {
56 'grid field': {
57 // update records from widgetcolumns
58 change: function(widget, value) {
59 let rec = widget.getWidgetRecord();
60 rec.set(widget.name, value);
61 rec.commit();
62 },
63 },
64 'pveStorageSelector': {
65 change: 'storageChange',
66 },
67 },
68 },
69
70 viewModel: {
71 data: {
72 coreCount: 1,
73 socketCount: 1,
74 warnings: [],
75 },
76
77 formulas: {
78 totalCoreCount: get => get('socketCount') * get('coreCount'),
79 hideWarnings: get => get('warnings').length === 0,
80 warningsText: get => '<ul style="margin: 0; padding-left: 20px;">'
81 + get('warnings').map(w => `<li>${w}</li>`).join('') + '</ul>',
82 },
83 },
84
85 items: [
86 {
87 xtype: 'inputpanel',
88 onGetValues: function(values) {
89 let me = this;
90 let grid = me.up('pveGuestImportWindow');
91
92 let config = Ext.apply(grid.vmConfig, values);
93
94 if (config.scsi0) {
95 config.scsi0 = config.scsi0.replace('local:0,', 'local:0,format=qcow2,');
96 }
97
98 grid.lookup('diskGrid').getStore().each((rec) => {
99 if (!rec.data.enable) {
100 return;
101 }
102 let id = rec.data.id;
103 delete rec.data.enable;
104 delete rec.data.id;
105 rec.data.file += ':0'; // for our special api format
106 if (id === 'efidisk0') {
107 delete rec.data['import-from'];
108 }
109 config[id] = PVE.Parser.printQemuDrive(rec.data);
110 });
111
112 grid.lookup('netGrid').getStore().each((rec) => {
113 if (!rec.data.enable) {
114 return;
115 }
116 let id = rec.data.id;
117 delete rec.data.enable;
118 delete rec.data.id;
119 config[id] = PVE.Parser.printQemuNetwork(rec.data);
120 });
121
122 if (grid.lookup('liveimport').getValue()) {
123 config['live-restore'] = 1;
124 }
125
126 return config;
127 },
128
129 column1: [
130 {
131 xtype: 'pveGuestIDSelector',
132 name: 'vmid',
133 fieldLabel: 'VM',
134 guestType: 'qemu',
135 loadNextFreeID: true,
136 },
137 {
138 xtype: 'proxmoxintegerfield',
139 fieldLabel: gettext('Sockets'),
140 name: 'sockets',
141 reference: 'socketsField',
142 value: 1,
143 minValue: 1,
144 maxValue: 4,
145 allowBlank: true,
146 bind: {
147 value: '{socketCount}',
148 },
149 },
150 {
151 xtype: 'proxmoxintegerfield',
152 fieldLabel: gettext('Cores'),
153 name: 'cores',
154 reference: 'coresField',
155 value: 1,
156 minValue: 1,
157 maxValue: 128,
158 allowBlank: true,
159 bind: {
160 value: '{coreCount}',
161 },
162 },
163 {
164 xtype: 'pveMemoryField',
165 fieldLabel: gettext('Memory'),
166 name: 'memory',
167 reference: 'memoryField',
168 value: 512,
169 allowBlank: true,
170 },
171 ],
172
173 column2: [
174 {
175 xtype: 'textfield',
176 fieldLabel: gettext('Name'),
177 name: 'name',
178 vtype: 'DnsName',
179 reference: 'nameField',
180 allowBlank: true,
181 },
182 {
183 xtype: 'CPUModelSelector',
184 name: 'cpu',
185 reference: 'cputype',
186 value: 'x86-64-v2-AES',
187 fieldLabel: gettext('Type'),
188 },
189 {
190 xtype: 'displayfield',
191 fieldLabel: gettext('Total cores'),
192 name: 'totalcores',
193 isFormField: false,
194 bind: {
195 value: '{totalCoreCount}',
196 },
197 },
198 ],
199 columnB: [
200 {
201 xtype: 'displayfield',
202 fieldLabel: gettext('Disks'),
203 labelWidth: 200,
204 },
205 {
206 xtype: 'grid',
207 reference: 'diskGrid',
208 maxHeight: 150,
209 store: { data: [] },
210 columns: [
211 {
212 xtype: 'checkcolumn',
213 header: gettext('Use'),
214 width: 50,
215 dataIndex: 'enable',
216 listeners: {
217 checkchange: function(_column, _rowIndex, _checked, record) {
218 record.commit();
219 },
220 },
221 },
222 {
223 text: gettext('Disk'),
224 dataIndex: 'id',
225 },
226 {
227 text: gettext('Source'),
228 dataIndex: 'import-from',
229 flex: 1,
230 renderer: function(value) {
231 return value.replace(/^.*\//, '');
232 },
233 },
234 {
235 text: gettext('Storage'),
236 dataIndex: 'file',
237 xtype: 'widgetcolumn',
238 width: 150,
239 widget: {
240 xtype: 'pveStorageSelector',
241 isFormField: false,
242 name: 'file',
243 storageContent: 'images',
244 },
245 onWidgetAttach: 'setNodename',
246 },
247 {
248 text: gettext('Format'),
249 dataIndex: 'format',
250 xtype: 'widgetcolumn',
251 width: 150,
252 widget: {
253 xtype: 'pveDiskFormatSelector',
254 name: 'format',
255 isFormField: false,
256 matchFieldWidth: false,
257 },
258 },
259 ],
260 },
261 {
262 xtype: 'displayfield',
263 fieldLabel: gettext('Network Interfaces'),
264 labelWidth: 200,
265 },
266 {
267 xtype: 'grid',
268 maxHeight: 150,
269 reference: 'netGrid',
270 store: { data: [] },
271 columns: [
272 {
273 xtype: 'checkcolumn',
274 header: gettext('Use'),
275 width: 50,
276 dataIndex: 'enable',
277 listeners: {
278 checkchange: function(_column, _rowIndex, _checked, record) {
279 record.commit();
280 },
281 },
282 },
283 {
284 text: gettext('ID'),
285 dataIndex: 'id',
286 },
287 {
288 text: gettext('MAC address'),
289 flex: 1,
290 dataIndex: 'macaddr',
291 },
292 {
293 text: gettext('Model'),
294 flex: 1,
295 dataIndex: 'model',
296 },
297 {
298 text: gettext('Bridge'),
299 dataIndex: 'bridge',
300 xtype: 'widgetcolumn',
301 widget: {
302 xtype: 'PVE.form.BridgeSelector',
303 name: 'bridge',
304 isFormField: false,
305 allowBlank: false,
306 },
307 onWidgetAttach: 'setNodename',
308 },
309 ],
310 },
311 {
312 xtype: 'displayfield',
313 fieldLabel: gettext('Warnings'),
314 labelWidth: 200,
315 hidden: true,
316 bind: {
317 hidden: '{hideWarnings}',
318 },
319 },
320 {
321 xtype: 'displayfield',
322 reference: 'warningText',
323 userCls: 'pmx-hint',
324 hidden: true,
325 bind: {
326 hidden: '{hideWarnings}',
327 value: '{warningsText}',
328 },
329 },
330 ],
331 },
332 ],
333
334 initComponent: function() {
335 let me = this;
336
337 if (!me.volumeName) {
338 throw "no volumeName given";
339 }
340
341 if (!me.storage) {
342 throw "no storage given";
343 }
344
345 if (!me.nodename) {
346 throw "no nodename given";
347 }
348
349 me.callParent();
350
351 me.query('toolbar')?.[0]?.insert(0, {
352 xtype: 'proxmoxcheckbox',
353 reference: 'liveimport',
354 boxLabelAlign: 'before',
355 boxLabel: gettext('Live Import'),
356 });
357
358 me.setTitle(Ext.String.format(gettext('Import Guest - {0}'), `${me.storage}:${me.volumeName}`));
359
360 let renderWarning = w => {
361 const warningsCatalogue = {
362 'cdrom-image-ignored': gettext("CD-ROM images cannot get imported, please reconfigure the '{0}' drive after the import"),
363 'nvme-unsupported': gettext("NVMe disks are currently not supported, '{0}' will get attaced as SCSI"),
364 'ovmf-with-lsi-unsupported': gettext("OVMF is built without LSI drivers, scsi hardware was set to '{1}'"),
365 'serial-port-socket-only': gettext("Serial socket '{0}' will be mapped to a socket"),
366 };
367 let message = warningsCatalogue[w.type];
368 if (!w.type || !message) {
369 return w.message ?? w.type ?? gettext('Unknown warning');
370 }
371 return Ext.String.format(message, w.key ?? 'unknown', w.value ?? 'unknown');
372 };
373
374 me.load({
375 success: function(response) {
376 let data = response.result.data;
377 me.vmConfig = data['create-args'];
378
379 let disks = [];
380 for (const [id, value] of Object.entries(data.disks ?? {})) {
381 disks.push({
382 id,
383 enable: true,
384 'import-from': id === 'efidisk0' ? Ext.htmlEncode('<none>') : value,
385 format: 'raw',
386 });
387 }
388
389 let nets = [];
390 for (const [id, parsed] of Object.entries(data.net ?? {})) {
391 parsed.id = id;
392 parsed.enable = true;
393 nets.push(parsed);
394 }
395 me.lookup('diskGrid').getStore().setData(disks);
396 me.lookup('netGrid').getStore().setData(nets);
397
398 me.getViewModel().set('warnings', data.warnings.map(w => renderWarning(w)));
399
400 me.setValues(me.vmConfig);
401 },
402 });
403 },
404 });