]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/window/Migrate.js
ui: window/Migrate: eslint fixes and code cleanup/refactoring/modernize
[pve-manager.git] / www / manager6 / window / Migrate.js
CommitLineData
04c32bed
DM
1Ext.define('PVE.window.Migrate', {
2 extend: 'Ext.window.Window',
3
9706707d
TM
4 vmtype: undefined,
5 nodename: undefined,
6 vmid: undefined,
8b4987e5 7 maxHeight: 450,
9706707d
TM
8
9 viewModel: {
10 data: {
11 vmid: undefined,
12 nodename: undefined,
13 vmtype: undefined,
14 running: false,
15 qemu: {
16 onlineHelp: 'qm_migration',
f6710aac 17 commonName: 'VM',
9706707d
TM
18 },
19 lxc: {
20 onlineHelp: 'pct_migration',
f6710aac 21 commonName: 'CT',
9706707d
TM
22 },
23 migration: {
24 possible: true,
25 preconditions: [],
26 'with-local-disks': 0,
27 mode: undefined,
4ddaf290
TM
28 allowedNodes: undefined,
29 overwriteLocalResourceCheck: false,
f6710aac
TL
30 hasLocalResources: false,
31 },
9706707d
TM
32
33 },
34
35 formulas: {
36 setMigrationMode: function(get) {
8058410f 37 if (get('running')) {
9706707d
TM
38 if (get('vmtype') === 'qemu') {
39 return gettext('Online');
40 } else {
41 return gettext('Restart Mode');
42 }
43 } else {
44 return gettext('Offline');
45 }
46 },
47 setStorageselectorHidden: function(get) {
48 if (get('migration.with-local-disks') && get('running')) {
49 return false;
50 } else {
51 return true;
52 }
4ddaf290
TM
53 },
54 setLocalResourceCheckboxHidden: function(get) {
55 if (get('running') || !get('migration.hasLocalResources') ||
56 Proxmox.UserName !== 'root@pam') {
57 return true;
58 } else {
59 return false;
60 }
f6710aac
TL
61 },
62 },
2df7ea9f 63 },
2df7ea9f
EK
64
65 controller: {
66 xclass: 'Ext.app.ViewController',
67 control: {
68 'panel[reference=formPanel]': {
69 validityChange: function(panel, isValid) {
9706707d
TM
70 this.getViewModel().set('migration.possible', isValid);
71 this.checkMigratePreconditions();
f6710aac
TL
72 },
73 },
9706707d
TM
74 },
75
76 init: function(view) {
77 var me = this,
78 vm = view.getViewModel();
79
80 if (!view.nodename) {
81 throw "missing custom view config: nodename";
82 }
83 vm.set('nodename', view.nodename);
84
85 if (!view.vmid) {
86 throw "missing custom view config: vmid";
87 }
88 vm.set('vmid', view.vmid);
89
90 if (!view.vmtype) {
91 throw "missing custom view config: vmtype";
92 }
93 vm.set('vmtype', view.vmtype);
94
9706707d 95 view.setTitle(
f6710aac 96 Ext.String.format('{0} {1} {2}', gettext('Migrate'), vm.get(view.vmtype).commonName, view.vmid),
9706707d
TM
97 );
98 me.lookup('proxmoxHelpButton').setHelpConfig({
f6710aac 99 onlineHelp: vm.get(view.vmtype).onlineHelp,
9706707d
TM
100 });
101 me.checkMigratePreconditions();
102 me.lookup('formPanel').isValid();
9706707d
TM
103 },
104
8058410f 105 onTargetChange: function(nodeSelector) {
d8d4c14b 106 // Always display the storages of the currently seleceted migration target
9706707d
TM
107 this.lookup('pveDiskStorageSelector').setNodename(nodeSelector.value);
108 this.checkMigratePreconditions();
109 },
110
111 startMigration: function() {
112 var me = this,
113 view = me.getView(),
114 vm = me.getViewModel();
115
116 var values = me.lookup('formPanel').getValues();
117 var params = {
f6710aac 118 target: values.target,
9706707d
TM
119 };
120
121 if (vm.get('migration.mode')) {
122 params[vm.get('migration.mode')] = 1;
123 }
124 if (vm.get('migration.with-local-disks')) {
125 params['with-local-disks'] = 1;
126 }
11c17a01
FE
127 //offline migration to a different storage currently might fail at a late stage
128 //(i.e. after some disks have been moved), so don't expose it yet in the GUI
129 if (vm.get('migration.with-local-disks') && vm.get('running') && values.targetstorage) {
9706707d
TM
130 params.targetstorage = values.targetstorage;
131 }
132
4ddaf290 133 if (vm.get('migration.overwriteLocalResourceCheck')) {
399ffa76 134 params.force = 1;
4ddaf290
TM
135 }
136
9706707d
TM
137 Proxmox.Utils.API2Request({
138 params: params,
139 url: '/nodes/' + vm.get('nodename') + '/' + vm.get('vmtype') + '/' + vm.get('vmid') + '/migrate',
140 waitMsgTarget: view,
141 method: 'POST',
142 failure: function(response, opts) {
143 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
144 },
145 success: function(response, options) {
146 var upid = response.result.data;
147 var extraTitle = Ext.String.format(' ({0} ---> {1})', vm.get('nodename'), params.target);
148
149 Ext.create('Proxmox.window.TaskViewer', {
150 upid: upid,
f6710aac 151 extraTitle: extraTitle,
9706707d
TM
152 }).show();
153
154 view.close();
f6710aac 155 },
9706707d 156 });
9706707d
TM
157 },
158
4ddaf290 159 checkMigratePreconditions: function(resetMigrationPossible) {
9706707d
TM
160 var me = this,
161 vm = me.getViewModel();
162
9706707d
TM
163 var vmrec = PVE.data.ResourceStore.findRecord('vmid', vm.get('vmid'),
164 0, false, false, true);
165 if (vmrec && vmrec.data && vmrec.data.running) {
166 vm.set('running', true);
167 }
168
169 if (vm.get('vmtype') === 'qemu') {
4ddaf290 170 me.checkQemuPreconditions(resetMigrationPossible);
9706707d 171 } else {
4ddaf290 172 me.checkLxcPreconditions(resetMigrationPossible);
9706707d
TM
173 }
174 me.lookup('pveNodeSelector').disallowedNodes = [vm.get('nodename')];
175
176 // Only allow nodes where the local storage is available in case of offline migration
177 // where storage migration is not possible
178 me.lookup('pveNodeSelector').allowedNodes = vm.get('migration.allowedNodes');
179
180 me.lookup('formPanel').isValid();
9706707d
TM
181 },
182
d8d4c14b
TL
183 checkQemuPreconditions: async function(resetMigrationPossible) {
184 let me = this,
9706707d
TM
185 vm = me.getViewModel(),
186 migrateStats;
187
188 if (vm.get('running')) {
189 vm.set('migration.mode', 'online');
190 }
191
d8d4c14b
TL
192 try {
193 let { result } = await Proxmox.Async.api2({
194 url: `/nodes/${vm.get('nodename')}/${vm.get('vmtype')}/${vm.get('vmid')}/migrate`,
195 method: 'GET',
196 });
197 migrateStats = result.data;
198 } catch (error) {
199 Ext.Msg.alert(gettext('Error'), error.htmlStatus);
200 return;
201 }
2df7ea9f 202
d8d4c14b
TL
203 if (migrateStats.running) {
204 vm.set('running', true);
205 }
206 // Get migration object from viewmodel to prevent to many bind callbacks
207 let migration = vm.get('migration');
208 if (resetMigrationPossible) {
209 migration.possible = true;
210 }
211 migration.preconditions = [];
212
213 if (migrateStats.allowed_nodes) {
214 migration.allowedNodes = migrateStats.allowed_nodes;
215 let target = me.lookup('pveNodeSelector').value;
216 if (target.length && !migrateStats.allowed_nodes.includes(target)) {
217 let disallowed = migrateStats.not_allowed_nodes[target];
218 let missingStorages = disallowed.unavailable_storages.join(', ');
219
220 migration.possible = false;
221 migration.preconditions.push({
222 text: 'Storage (' + missingStorages + ') not available on selected target. ' +
223 'Start VM to use live storage migration or select other target node',
224 severity: 'error',
225 });
226 }
227 }
228
229 if (migrateStats.local_resources.length) {
230 migration.hasLocalResources = true;
231 if (!migration.overwriteLocalResourceCheck || vm.get('running')) {
232 migration.possible = false;
233 migration.preconditions.push({
234 text: Ext.String.format('Can\'t migrate VM with local resources: {0}',
235 migrateStats.local_resources.join(', ')),
236 severity: 'error',
237 });
238 } else {
239 migration.preconditions.push({
240 text: Ext.String.format('Migrate VM with local resources: {0}. ' +
241 'This might fail if resources aren\'t available on the target node.',
242 migrateStats.local_resources.join(', ')),
243 severity: 'warning',
244 });
245 }
246 }
9706707d 247
d8d4c14b
TL
248 if (migrateStats.local_disks.length) {
249 migrateStats.local_disks.forEach(function(disk) {
250 if (disk.cdrom && disk.cdrom === 1) {
251 if (disk.volid.includes('vm-' + vm.get('vmid') + '-cloudinit')) {
252 if (migrateStats.running) {
253 migration.possible = false;
254 migration.preconditions.push({
255 text: "Can't live migrate VM with local cloudinit disk, use shared storage instead",
256 severity: 'error',
257 });
258 }
259 } else {
4ddaf290
TM
260 migration.possible = false;
261 migration.preconditions.push({
d8d4c14b 262 text: "Can't migrate VM with local CD/DVD",
f6710aac 263 severity: 'error',
4ddaf290 264 });
4ddaf290 265 }
d8d4c14b
TL
266 } else {
267 let size = disk.size ? '(' + Proxmox.Utils.render_size(disk.size) + ')' : '';
268 migration['with-local-disks'] = 1;
269 migration.preconditions.push({
270 text: Ext.String.format('Migration with local disk might take long: {0} {1}', disk.volid, size),
271 severity: 'warning',
9706707d 272 });
9706707d 273 }
d8d4c14b
TL
274 });
275 }
9706707d 276
d8d4c14b 277 vm.set('migration', migration);
9706707d 278 },
4ddaf290 279 checkLxcPreconditions: function(resetMigrationPossible) {
d8d4c14b 280 let vm = this.getViewModel();
9706707d
TM
281 if (vm.get('running')) {
282 vm.set('migration.mode', 'restart');
04c32bed 283 }
f6710aac 284 },
2df7ea9f 285 },
04c32bed 286
3188b0b7 287 width: 600,
2df7ea9f 288 modal: true,
9706707d
TM
289 layout: {
290 type: 'vbox',
f6710aac 291 align: 'stretch',
9706707d 292 },
2df7ea9f 293 border: false,
2df7ea9f
EK
294 items: [
295 {
296 xtype: 'form',
297 reference: 'formPanel',
04c32bed
DM
298 bodyPadding: 10,
299 border: false,
d27a44a6 300 layout: 'hbox',
04c32bed
DM
301 items: [
302 {
9706707d 303 xtype: 'container',
d27a44a6 304 flex: 1,
9706707d 305 items: [{
090dff2f
TL
306 xtype: 'displayfield',
307 name: 'source',
308 fieldLabel: gettext('Source node'),
309 bind: {
f6710aac
TL
310 value: '{nodename}',
311 },
9706707d
TM
312 },
313 {
314 xtype: 'displayfield',
315 reference: 'migrationMode',
316 fieldLabel: gettext('Mode'),
317 bind: {
f6710aac
TL
318 value: '{setMigrationMode}',
319 },
320 }],
04c32bed
DM
321 },
322 {
9706707d 323 xtype: 'container',
d27a44a6 324 flex: 1,
090dff2f
TL
325 items: [{
326 xtype: 'pveNodeSelector',
327 reference: 'pveNodeSelector',
328 name: 'target',
329 fieldLabel: gettext('Target node'),
330 allowBlank: false,
331 disallowedNodes: undefined,
332 onlineValidator: true,
333 listeners: {
f6710aac
TL
334 change: 'onTargetChange',
335 },
090dff2f
TL
336 },
337 {
338 xtype: 'pveStorageSelector',
339 reference: 'pveDiskStorageSelector',
340 name: 'targetstorage',
341 fieldLabel: gettext('Target storage'),
342 storageContent: 'images',
11c17a01
FE
343 allowBlank: true,
344 autoSelect: false,
cd338fe1 345 emptyText: gettext('Current layout'),
090dff2f 346 bind: {
f6710aac
TL
347 hidden: '{setStorageselectorHidden}',
348 },
4ddaf290
TM
349 },
350 {
351 xtype: 'proxmoxcheckbox',
352 name: 'overwriteLocalResourceCheck',
353 fieldLabel: gettext('Force'),
354 autoEl: {
355 tag: 'div',
f6710aac 356 'data-qtip': 'Overwrite local resources unavailable check',
4ddaf290
TM
357 },
358 bind: {
359 hidden: '{setLocalResourceCheckboxHidden}',
f6710aac 360 value: '{migration.overwriteLocalResourceCheck}',
4ddaf290
TM
361 },
362 listeners: {
d8d4c14b
TL
363 change: {
364 fn: 'checkMigratePreconditions',
365 extraArg: true,
366 },
f6710aac
TL
367 },
368 }],
369 },
370 ],
9706707d
TM
371 },
372 {
373 xtype: 'gridpanel',
374 reference: 'preconditionGrid',
b4533390 375 selectable: false,
9706707d 376 flex: 1,
b4533390
TL
377 columns: [{
378 text: '',
379 dataIndex: 'severity',
380 renderer: function(v) {
381 switch (v) {
382 case 'warning':
383 return '<i class="fa fa-exclamation-triangle warning"></i> ';
384 case 'error':
385 return '<i class="fa fa-times critical"></i>';
386 default:
387 return v;
388 }
389 },
f6710aac 390 width: 35,
b4533390
TL
391 },
392 {
393 text: 'Info',
394 dataIndex: 'text',
395 cellWrap: true,
f6710aac 396 flex: 1,
b4533390 397 }],
9706707d
TM
398 bind: {
399 hidden: '{!migration.preconditions.length}',
400 store: {
cd338fe1
TL
401 fields: ['severity', 'text'],
402 data: '{migration.preconditions}',
403 sorters: 'text',
f6710aac
TL
404 },
405 },
406 },
9706707d 407
2df7ea9f
EK
408 ],
409 buttons: [
410 {
672a6270
TL
411 xtype: 'proxmoxHelpButton',
412 reference: 'proxmoxHelpButton',
2df7ea9f
EK
413 onlineHelp: 'pct_migration',
414 listenToGlobalEvent: false,
f6710aac 415 hidden: false,
2df7ea9f
EK
416 },
417 '->',
418 {
419 xtype: 'button',
420 reference: 'submitButton',
9706707d
TM
421 text: gettext('Migrate'),
422 handler: 'startMigration',
423 bind: {
f6710aac
TL
424 disabled: '{!migration.possible}',
425 },
426 },
427 ],
3188b0b7 428});