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